TL; DR
昨日初めてRustを触ってみたメモと備忘録です。
まだ序盤なので全然少ないのですが、随時追記していこうと思います。
※ 2020/01/05 追記
コマンド
インストールした際に追加されるコマンド群
リリースチャンネルとしてstable
/beta
/nightly
があり、各チャンネルは以下のようになっている。
- stable : 安定板
- beta : nightlyのテストが完了したもの
- nightly : プレビュー版
基本的にはstableを使うことをおすすめする。
rustc
ソース : https://github.com/rust-lang/rust/tree/master/src/rustc
Rust言語のコンパイラで、ソースコードからライブラリやバイナリファイルを生成する。rustc hoge.rs
でビルドが実施できる。
cargo build
も同様なので、こちらが使われることが多い。
単体でサクッと試したいときはいいかも。
https://doc.rust-lang.org/rustc/what-is-rustc.html
rustup
ソース : https://github.com/rust-lang/rustup
RustのToolchainインストーラーでデフォルトの場合rustc
/cargo
/rustup
をインストールする。rust update
でtoolchainやrustupのアップデートができる。
リリースチャンネルを指定して、インストールやビルドが実行できる。
# nightlyからインストール
$ rustup install nightly
# betaでビルド
$ rustup run beta cargo run
cargo
パッケージ管理およびビルド実行ツールとして利用できる。上述したrustc
はほぼほぼこっちに駆逐されている気がする。新規プロジェクトの作成やテスト、お片づけなどもしてくれる優れもの。
主要コマンド
-
cargo new
: 新規プロジェクトを作成する -
cargo build
: ソースコードをビルド -
cargo run
: ビルドファイルを実行する -
cargo test
: テストを実行する
fn main(x: i32) -> i32 {
x * 4
}
# [cfg(test)] //テストであることを宣言
mod tests { //テストモジュールとしてグループ化
use super::*;
#[test] //テストパターン
fn succeed_test() {
assert_eq!(8, main(2));
}
#[test]
fn failure_test() {
assert_eq!(4, main(2));
}
}
$ cargo test
Compiling cargo_test v0.1.0 (/Users/user/into-rust/cargo_test)
Finished test [unoptimized + debuginfo] target(s) in 0.51s
Running target/debug/deps/cargo_test-f3e16b982542493f
running 2 tests
test tests::succeed_test ... ok //成功パターンが成功していることがわかる
test tests::failure_test ... FAILED //失敗パターンが失敗していることがわかる
failures:
---- tests::failure_test stdout ----
thread 'tests::failure_test' panicked at 'assertion failed: `(left == right)`
left: `4`,
right: `8`', src/main.rs:16:9
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace.
failures:
tests::failure_test
test result: FAILED. 1 passed; 1 failed; 0 ignored; 0 measured; 0 filtered out
error: test failed, to rerun pass '--bin cargo_test'
-
cargo clippy
: 怪しげなやつを教えてくれる
$ cargo clippy
warning: redundant field names in struct initialization
--> src/main.rs:15:13
|
15 | left: left,
| ^^^^^^^^^^ help: replace it with: `left`
|
= note: `#[warn(clippy::redundant_field_names)]` on by default
= help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#redundant_field_names
warning: redundant field names in struct initialization
--> src/main.rs:16:13
|
16 | right: right,
| ^^^^^^^^^^^^ help: replace it with: `right`
|
= help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#redundant_field_names
Finished dev [unoptimized + debuginfo] target(s) in 0.01s
-
cargo clean
:cargo run
等で生成されたtargetを消してくれる。
$ ls -l
total 16
drwxr-xr-x 5 user staff 160 1 3 10:40 .
drwxr-xr-x 3 user staff 96 12 30 12:41 ..
-rw-r--r-- 1 user staff 142 12 30 12:41 Cargo.lock
-rw-r--r-- 1 user staff 233 12 30 12:41 Cargo.toml
drwxr-xr-x 3 user staff 96 12 30 11:08 src
$
$ cargo run
Compiling helloworld v0.1.0 (/Users/user/Documents/GitHub/into-rust/cargo_new/helloworld)
Finished dev [unoptimized + debuginfo] target(s) in 0.34s
Running `target/debug/helloworld` # バイナリファイルを含むtagetが生成される
hello, world
$
$ ls -l
total 16
drwxr-xr-x 6 user staff 192 1 3 10:40 .
drwxr-xr-x 3 user staff 96 12 30 12:41 ..
-rw-r--r-- 1 user staff 142 12 30 12:41 Cargo.lock
-rw-r--r-- 1 user staff 233 12 30 12:41 Cargo.toml
drwxr-xr-x 3 user staff 96 12 30 11:08 src
drwxr-xr-x 4 user staff 128 1 3 10:40 target # Githubとかにあげるときには不要
$ cargo clean
$
$ ls -l
total 16
drwxr-xr-x 5 user staff 160 1 3 10:40 .
drwxr-xr-x 3 user staff 96 12 30 12:41 ..
-rw-r--r-- 1 user staff 142 12 30 12:41 Cargo.lock
-rw-r--r-- 1 user staff 233 12 30 12:41 Cargo.toml
drwxr-xr-x 3 user staff 96 12 30 11:08 src # targetごと削除されたのがわかる
rustfmt
任意の設定に応じてファイルを整形してくれる。
設定ファイルの置き場は下記を参照
※macは$HOME/Library/Preferences
https://docs.rs/dirs/1.0.4/dirs/fn.config_dir.html
上記配下にrustfmt/rustfmt.toml
をおけばOK
設定項目については以下を参照されたし。
https://github.com/rust-lang/rustfmt/blob/master/Configurations.md
tab_spaces = 2
error_on_line_overflow = true
error_on_unformatted = true
version = "Two"
Syntax
*.rs
ファイルにおける記法および構成要素
変数束縛
Rustの場合key:valueが1:1になる。
また変数はデフォルトでイミュータブルなものとして扱われる。
型を明示的に書く場合はkeyのあとにコロンで指定する。
※型推論が効くので不要な場合も多い
fn main() {
let x: i32 = 5;
}
変数に再代入などをしたい場合はmut
を用いて可変なものである、ということを明示してあげる必要がある。
fn main() {
let mut x: i32 = 5;
let x = 10;
}
変数束縛はブロック内でしか有効でないインスタンス変数のような特性を持つ。
また同じ名前の変数束縛がある場合、後続の方に上書きされる。この際、違う型へ束縛することも可能。これをシャドーイングと言う。
fn main() {
let mut x: i32 = 1;
println!("{}", x);
x = 7;
let x = x;
let y = 4;
println!("{}", y);
let y = "variable change intenger to string";
println!("{}", x);
println!("{}", y);
}
=================result=================
$ cargo run
Compiling helloworld v0.1.0 (/Users/user/into-rust/cargo_new/helloworld)
Finished dev [unoptimized + debuginfo] target(s) in 0.28s
Running `target/debug/helloworld`
1
4
7
variable change intenger to string
プリミティブ型
Rustには様々な型があり、それらが言語に組み込まれている。
Primitive Types
- boolean型
let x: bool = true; // or false;
true/falseの型で主にif条件文で用いられる。
- char型
let x = 'x';
let stars = '✨';
シングルクオートで囲われたユニコードのスカラ値を表す型で、Rustの場合4バイトであることを意味する。
- intenger型
category | class |
---|---|
符号あり |
i8 /i16 /i32 /i64
|
符号なし |
u8 /u16 /u32 /u64
|
可変長型 |
isize /usize
|
浮動小数点 |
f32 /f64
|
- array型
// Basic array
let a = [1, 2, 3];
let mut m = [1, 2, 3];
// [T; N] array
let a = [0; 20]; // 変数の各要素を0で初期化
- slice構文
スライスの要素として&
と[]
がある。
&
はスライスを参照することを表す。
[]
は範囲を持ち、スライスの長さを定義する。
let a = [0, 1, 2, 3, 4];
let complete = &a[..]; // aに含まれる全ての要素を持つスライス
let middle = &a[1..4]; // 1、2、3のみを要素に持つaのスライス
- str型
文字列には2種類あり、プリミティブ型のstr
と標準ライブラリのString
が存在する。
&str
の場合、文字列スライスとして扱う。
fn main() {
let x = "Hello ".to_string();
let y = "world!".to_string();
println!("{}", x + &y);
}
====================result====================
$ cargo run
Compiling helloworld v0.1.0 (/Users/user/into-rust/cargo_new/helloworld)
Finished dev [unoptimized + debuginfo] target(s) in 0.46s
Running `target/debug/helloworld`
Hello world!
- tuple型
タプル型は固定サイズの順序かつリストとなるもの。
下記例のようにインデックス構文でアクセスすることも可能だが、その場合[]
ではなく.
でアクセスする
fn main() {
let x = (1, "hello");
// 型注釈も可能
let y: (i32, &str) = (2, "world");
println!("{}", x.0);
println!("{}", x.1);
println!("{}", y.0);
println!("{}", y.1);
}
=======================result=======================
$ cargo run
Compiling helloworld v0.1.0 (/Users/user/into-rust/cargo_new/helloworld)
Finished dev [unoptimized + debuginfo] target(s) in 0.26s
Running `target/debug/helloworld`
1
hello
2
world
型が一致していれば他のタプルに入れ込むことも可能
fn main() {
let mut x: (i32, &str) = (1, "hello");
let y: (i32, &str) = (2, "3");
x = y;
println!("{}", x.0);
println!("{}", x.1);
}
---------------------result--------------------
$ cargo run
Compiling helloworld v0.1.0 (/Users/user/into-rust/cargo_new/helloworld)
Finished dev [unoptimized + debuginfo] target(s) in 0.27s
Running `target/debug/helloworld`
2
3
=====================型不一致====================
fn main() {
let mut x: (i32, &str) = (1, "hello");
let y: (i32, i32) = (2, 3);
x = y;
println!("{}", x.0);
println!("{}", x.1);
}
---------------------result--------------------
$ cargo run
Compiling helloworld v0.1.0 (/Users/user/into-rust/cargo_new/helloworld)
error[E0308]: mismatched types
--> src/main.rs:5:9
|
5 | x = y;
| ^ expected &str, found i32
|
= note: expected type `(i32, &str)`
found type `(i32, i32)`
error: aborting due to previous error
またタプルの各要素を変数として定義してアクセスできる分配束縛というものもある。
let (x, y, z) = (1, 2, 3);
println!("x is {}", x);
- 関数
関数も型注釈を入れることができる
fn foo(x: i32) -> i32 { x }
let x: fn(i32) -> i32 = foo;
vec(ベクタ)
ベクタは動的な配列であり拡張可能な配列です。標準ライブラリで提供されている。
インデックス構文で要素にアクセスが可能だが、その際インデックスはusize
型でなければいけない。
fn main() {
let x = vec![1, 2, 3, 4, 5];
let y = vec![1, 2, 3, 4, 5];
println!("{}", x[0]);
println!("{}", y[3]);
println!("{}", x[0] + y[3]);
}
================result================
$ cargo run
Compiling helloworld v0.1.0 (/Users/user/into-rust/cargo_new/helloworld)
Finished dev [unoptimized + debuginfo] target(s) in 0.42s
Running `target/debug/helloworld`
1
4
5
fn main() {
let x = vec![1, 2, 3, 4, 5];
let y = vec![1, 2, 3, 4, 5];
let a: usize = 0;
let b: i32 = 3;
println!("{}", x[a]);
println!("{}", y[b]);
println!("{}", x[a] + y[b]);
}
================result================
$ cargo run
Compiling helloworld v0.1.0 (/Users/user/into-rust/cargo_new/helloworld)
error[E0277]: the type `[{integer}]` cannot be indexed by `i32`
--> src/main.rs:9:20
|
9 | println!("{}", y[b]);
| ^^^^ slice indices are of type `usize` or ranges of `usize`
|
= help: the trait `std::slice::SliceIndex<[{integer}]>` is not implemented for `i32`
= note: required because of the requirements on the impl of `std::ops::Index<i32>` for `std::vec::Vec<{integer}>`
for
を用いての繰り返しも可能
fn main() {
let mut x = vec![1, 2, 3, 4, 5];
for i in &x {
println!("A reference to {}", i);
}
for i in &mut x {
println!("A mutable reference to {}", i);
}
for i in x {
println!("Take ownership of the vector and its element {}", i);
}
}
impl(メソッド呼び出し)
impl
構文を利用してメソッドを定義することができる。
その際第一引数に関してself
/&self
/&mut self
と定義することが可能だが、基本的に借用を利用しイミュータブルな参照を渡すべきなので&self
を置くのが基本
impl Circle {
fn area(&self) -> f64 {
std::f64::consts::PI * (self.radius * self.radius)
}
}
crate(クレート)
クレートとは他の言語におけるライブラリやパッケージにあたるもの。
参照させるには色々なやり方がある。
$ tree
.
├── Cargo.lock
├── Cargo.toml
└── src
├── chinese
│ ├── evening.rs
│ ├── mod.rs
│ └── moring.rs
├── lib.rs
├── main.rs
└── spanish
├── evening.rs
├── mod.rs
└── moring.rs
// main.rs
extern crate crate_demo;
fn main() {
println!("おはよう in Chinese: {}", crate_demo::chinese::morning::morning());
println!("こんばんは in Chinese: {}", crate_demo::chinese::evening::evening());
println!("おはよう in Spanish: {}", crate_demo::spanish::morning::morning());
println!("こんばんは in Spanish: {}", crate_demo::spanish::evening::evening());
}
// lib.rs
pub mod chinese;
pub mod spanish;
//*/mod.rs
pub mod morning;
pub mod evening;
//*/morning.rs
pub fn morning() -> String {
"hogehoge".to_string()
}
//*/evening.rs
pub fn evening() -> String {
"fugafuga".to_string()
}
もしくはuse
とself
を利用して
// main.rs
extern crate crate_demo;
use crate_demo::chinese::{morning, evening}; //複数クレートを指定してインポート
use crate_demo::spanish; //モジュール階層を指定してインポート
fn main() {
println!("おはよう in Chinese: {}", morning::morning());
println!("こんばんは in Chinese: {}", evening::evening());
println!("おはよう in Spanish: {}", spanish::morning());
println!("こんばんは in Spanish: {}", spanish::evening());
}
// lib.rs
pub mod chinese;
pub mod spanish;
//*/mod.rs
pub use self::morning::morning; //各クレートのスコープをモジュールの親階層にselfで設定している
pub use self::evening::evening;
mod morning;
mod evening;
//*/morning.rs
pub fn morning() -> String {
"hogehoge".to_string()
}
//*/evening.rs
pub fn evening() -> String {
"fugafuga".to_string()
}
use
を宣言した場合、ルートディレクトリからの絶対パスとして認識される。
self
は現在地からの相対パスとして認識される。
mod(モジュール)
下記のようなディレクトリ構成の場合、hoge.rs
を指定してビルドする必要はなくrustc main.rs
を実行するだけで意図した結果が得られる。
モジュール名にはlower_snake_caseを利用する。
モジュールはデフォルトでプライベートなため、他の関数などから参照するにはpub
を先頭につける必要がある。配下のサブモジュールや関数は::
で参照ができる。
$ tree
.
├── hoge.rs
├── main
└── main.rs
mod hoge;
fn main() {
hoge::hello();
}
pub fn hello() {
println!("Hello, world!");
}
$ ./main
Hello, world!
if文
shellとあんまり変わんないけど式で書けるので、下記みたいなものでも成り立つ。
let x = 5;
let y = if x == 5 {
10
} else {
15
};
// 上記のように無駄に改行しない
let x = 5;
let y = if x == 5 { 10 } else { 15 };
繰り返し
Rustの繰り返しには下記の記法がある。
loop
while
for
// 無限ループする
loop {
println!("Loop forever!");
}
// break;で終わらすことができる
let mut x = 5;
loop {
x += x - 3;
println!("{}", x);
if x % 5 == 0 { break; }
}
// continue;で次の処理を再開することができる
for x in 0..10 {
if x % 2 == 0 { continue; }
println!("{}", x);
}
// 5で割り切れるまで繰り返す
let mut x = 5;
let mut done = false;
while !done {
x += x - 3;
println!("{}", x);
if x % 5 == 0 {
done = true;
}
}
// 0から9まで出力する
for x in 0..10 {
println!("{}", x);
}
// 範囲指定して5から9まで出力する
for x in 5..10 {
println!("{}", x);
}
ループラベル
繰り返し対象にラベルをつけて実行対象を制御することができる。
'outer: for x in 0..10 {
'inner: for y in 0..10 {
if x % 2 == 0 { continue 'outer; } // x のループを継続
if y % 2 == 0 { continue 'inner; } // y のループを継続
println!("x: {}, y: {}", x, y);
}
}
構造体(struct)
Rustにはクラスがなくstruct
やimpl
、trait
で使い分ける。
struct
はパッと見オブジェクトっぽく見えるが、文字タイプの指定などを(構造化)しているだけで実際のデータ値を持っているわけではない。structの命名にはキャメルケースを使う。origin.x
のようなドット表記で値にアクセスすることが可能。
struct Point {
x: i32,
y: i32,
}
fn main() {
let origin = Point { x: 3, y: 2 };
println!("The origin is at ({}, {})", origin.x, origin.y);
}
structもやっぱりイミュータブルだけど、ミュータブルにもできる。
// OK例
fn main() {
let mut point = Point { x: 0, y: 0 };
point.x = 5;
println!("The point is at ({}, {})", point.x, point.y);
}
// NG例 言語レベルでフィールドの変更は許可していない。
struct Point {
mut x: i32,
y: i32,
}
アップデート構文
.
を利用して不要な変更を省略(デフォルトを利用)することができる。
struct Point3d {
x: i32,
y: i32,
z: i32,
}
let mut point = Point3d { x: 0, y: 0, z: 0 };
point = Point3d { y: 1, .. point }; // 変更が必要ない部分は..で省略している
let origin = Point3d { x: 0, y: 0, z: 0 };
let point = Point3d { z: 1, x: 2, .. origin };
タプル構造体
タプルを利用した構造体を指定することもできるが、フィールドが単一(newtypeパターン)か空(unit-like)の場合以外あまりオススメしない。
struct Color(i32, i32, i32);
struct Point(i32, i32, i32);
let black = Color(0, 0, 0);
let origin = Point(0, 0, 0);
// newtypeパターン
struct Inches(i32);
let length = Inches(10);
let Inches(integer_length) = length;
println!("length is {} inches", integer_length);
// unit-likeパターン
struct Electron;
let x = Electron;Run
列列挙(enum)
enum
構文は、struct
構文と似ているがstructとは違い色々なvariantを持つことが可能
- データを持たないヴァリアント
- 名前付きデータを持つヴァリアント
- 名前なしデータを持つヴァリアント
ヴァリアントの名前はenum自体の名前によってスコープ化されているため、各ヴァリアントの名前を使うために::
構文を使用する。
enum Message {
Quit,
ChangeColor(i32, i32, i32),
Move { x: i32, y: i32 },
Write(String),
}
let x: Message = Message::Move { x: 3, y: 4 }; //Message列列挙からMoveを呼び出している
enum BoardGameTurn {
Move { squares: i32 },
Pass,
}
let y: BoardGameTurn = BoardGameTurn::Move { squares: 1 }; //BoardGameTurn列列挙からMoveを呼び出している
// 関数っぽくも使える
let m = Message::Write("Hello, world".to_string());
マッチ(match)
パターンが多い場合if/else
だと冗長になりがちなものをmatch
構文で列挙することで条件分岐させることができる。マッチしない場合はpanic
になるのでエラーハンドリングを意識した書き方が必要になる。
またmatch
は式でもあるため、式形式で書くことで型変換を実施することもできる。
let x = 5; // 整数型
let number = match x { // match x { で始まると、_で型不一致によるビルドエラーが起こる
1 => "one",
2 => "two",
3 => "three",
4 => "four",
5 => "five",
_ => "something else",
};
enumへのマッチ
列列挙で記載された条件に対してもmatch構文で処理を実施することが可能
enum Message {
Quit,
ChangeColor(i32, i32, i32),
Move { x: i32, y: i32 },
Write(String),
}
fn quit() { /* ... */ }
fn change_color(r: i32, g: i32, b: i32) { /* ... */ }
fn move_cursor(x: i32, y: i32) { /* ... */ }
fn process_message(msg: Message) {
match msg {
Message::Quit => quit(),
Message::ChangeColor(r, g, b) => change_color(r, g, b),
Message::Move { x: x, y: y } => move_cursor(x, y),
Message::Write(s) => println!("{}", s),
};
}
その際にマッチの特性上、コンパイラが網羅的に列列挙のvariantに対するマッチの条件処理が記載されているかを確認するため一つでも処理が記載されていないもの or **_
を用いた例外処理(matchしないもの)**のいずれかを実装していない場合panicを起こすので注意が必要
パターン
変数束縛やマッチで利用されスコープの中で有効なため、シャドーイングされて処理が実行される。
またパイプを利用して複数条件を指定することができる。
let x = 1;
match x {
1 | 2 => println!("one or two"),
3 => println!("three"),
_ => println!("anything"),
}
println!("{}", x)
分配束縛
タプルや列挙型、構造体などの複合データをパターン内で要素ごとに分解することができる。
struct Point {
x: i32,
y: i32,
}
let origin = Point { x: 0, y: 0 };
match origin {
Point { x, y } => println!("({},{})", x, y),
}
// スコープ内で値に別名をつけることが可能
struct Point {
x: i32,
y: i32,
}
let origin = Point { x: 0, y: 0 };
match origin {
Point { x: x1, y: y1 } => println!("({},{})", x1, y1),
}
// 一部だけ取り出して処理することが可能
struct Point {
x: i32,
y: i32,
}
let origin = Point { x: 0, y: 0 };
match origin {
Point { x, .. } => println!("x is {}", x),
}
束縛の無視
_
や..
を利用して例外処理や一部分のみを束縛することができる。
// アンダースコアパターン
match some_value {
Ok(value) => println!("got a value: {}", value), // OKのvariant値をvalueで束縛している
Err(_) => println!("an error occurred"), // Errのvariant値を_を利用して例外を処理している
}
// ドットパターン
enum OptionalTuple {
Value(i32, i32, i32),
}
let x = OptionalTuple::Value(5, -2, 3);
match x {
OptionalTuple::Value(..) => println!("Got a tuple!")
}
マッチの色々な書き方
- refとref mut
refを利用して参照を取得することが可能
let x = 5;
match x {
ref r => println!("Got a reference to {}", r),
}
// ミュータブルな参照(mut T)をする場合場合
let mut y = 5:
match y {
ref mut mr => println!("Got a mutable reference to {}", mr),
}
- 範囲
文字列で使われることが多いパターンで...
を利用して範囲を示す
let x = '💅';
match x {
'a' ... 'j' => println!("early letter"),
'k' ... 'z' => println!("late letter"),
_ => println!("something else"),
}
- 束縛
@
で値に名前を束縛することができる。
let x = 1;
match x {
e @ 1 ... 5 => println!("got a range element {}", e), // 1から5をeとして束縛
_ => println!("anything"),
}
# [derive(Debug)]
struct Person {
name: Option<String>,
}
let name = "Steve".to_string();
let mut x: Option<Person> = Some(Person { name: Some(name) });
match x {
Some(Person { name: ref a @ Some(_), .. }) => println!("{:?}", a), // nameの値をaとして束縛し、参照した値をつっこむ
_ => {}
}
パイプ(|
)を利用した複数パターンを扱う場合は名前は一致しなければいけない
let x = 5;
// NG例
match x {
a @ 1 ... 5 | b @ 8 ... 10 => println!("got a range element {}", *), // 1..5と8..10の名前が一致していない
_ => println!("anything"),
}
// OK例
match x {
e @ 1 ... 5 | e @ 8 ... 10 => println!("got a range element {}", e),
_ => println!("anything"),
}
- ガード
if
を利用してマッチガードを実装できる
enum OptionalInt {
Value(i32),
Missing,
}
fn main() {
let x = OptionalInt::Value(7);
match x {
OptionalInt::Value(i) if i > 5 => println!("Got an int bigger than five!"),
OptionalInt::Value(..) => println!("Got an int!"), // 上記のif文を満たさないためそれ以外(..)として処理される
OptionalInt::Missing => println!("No such luck."),
}
}
上記の例の場合..
は_
でも問題ない。
複式パターンで if を使うと、 if は | の両側に適用されます:
let x = 4;
let y = false;
match x {
4 | 5 if y => println!("yes"), // xは4ではあるが5ではないため例外(_)として処理される
_ => println!("no"),
}
上記のとおり|
を用いる場合、(4 | 5) if y => ...
として扱われ4 | (5 if y) => ...
としては扱われない。
文字列
Rustにおける文字列は動的なサイズを持つデータ構造であり、nullバイトを保持することもできる。
文字列はUTF-8のバイトストリームとしてエンコードされたユニコードのスカラ値のシーケンスである。
Rustの文字列は2種類あり、&str
(文字列スライス)とString
(ヒープアロケートされる文字列)が存在する。
&str
(文字列スライス)
&str
はUTF-8のバイトシーケンスへの参照のため、固定サイズであり変更不可な文字列となる。
let greeting = "Hello there.";
という文字列はlet greeting: &'static str = "Hello there.";
と同意であり&'static str
型を持つ。
つまりコンパイル時に静的に割り当てられているためプログラム内で一意となるため変更が不可になる。
また文字列リテラルは複数行に渡って記載することができる。
// 改行と行頭の空白を含む形式
let s = "foo
bar";
assert_eq!("foo\n bar", s);Run
// \ を使って空白と改行を削る
let s = "foo\
bar";
assert_eq!("foobar", s);
String
String
文字列は伸張可能であり、またUTF-8であることも保証されている。
String
は文字列スライスをto_string
メソッドで変換することで作成されるが、メモリアロケーションが発生するため必要以上に実施するのは控えるのが好ましい。
let mut s = "Hello".to_string(); // 変数sはStringとして認識される
println!("{}", s); // to_stringメソッドで変換しないとエラーになる
String は & によって &str に型強制されるが、&strの実装するトレイトを引数として取る関数に対しては自動では変換されないため&*
にて明示的に変換が必要になる。
// Stringの型強制
fn takes_slice(slice: &str) {
println!("Got: {}", slice);
}
fn main() {
let s = "Hello".to_string();
takes_slice(&s);
}
// 明示的な変換
use std::net::TcpStream;
TcpStream::connect("192.168.0.1:3000"); // 引数として&strが必要
let addr_string = "192.168.0.1:3000".to_string();
TcpStream::connect(&*addr_string); // addr_stringを&strに変換して渡す
- 文字列におけるインデクシング(非対応)
文字列に対するインデクシングは言語レベルで許可されていない。
理由はUTF-8をエンコードしているため、バイトが固定長でない可能性があるため。
ただしインデクシングに近いような記述は可能になっている。
fn main() {
let hachiko = "忠犬ハチ公";
let dog = hachiko.chars().nth(1).unwrap();;
for b in hachiko.as_bytes() {
print!("{}, ", b);
}
for c in hachiko.chars() {
print!("{}, ", c);
}
println!("{}", dog);
}
========================result========================
229, 191, 160, 231, 138, 172, 227, 131, 143, 227, 131, 129, 229, 133, 172,
忠, 犬, ハ, チ, 公,
犬
- スライシング
// OK例
fn main() {
let hachiko = "hachiko";
let name = &hachiko[1..4];
println!("{}", name);
}
// NG例(漢字の場合バイト数が固定長じゃないため)
fn main() {
let hachiko = "忠犬ハチ公";
let dog2 = &hachiko[..1];
println!("{}", dog2);
=====================result=====================
thread 'main' panicked at 'byte index 1 is not a char boundary; it is inside '忠' (bytes 0..3) of `忠犬ハチ公`'
- 連結
String
に対して&str
を末尾に連結するできる。
Deref による型強制という&String
が自動的に&str
に型強制される機能があり、String
同士を連結する場合下記のように&
で繋ぐ必要がある。
// String + &str
fn main() {
let hello = "Hello ".to_string();
let world = "world!";
let hello_world = hello + world;
}
println!("{}", hello_world);
// String + String
fn main() {
let hello = "Hello ".to_string();
let world = "world!".to_string();
let hello_world = hello + &world;
println!("{}", hello_world);
}
所有権/参照と借用/ライフタイム
はこちら
内容は下記
所有権
- 変数束縛のムーブセマンティクス
- copy型
参照と借用
-
&T
型による参照と所有権の借用 -
&mut T
によるミュータブルな参照 - 借用のルールとスコープ
- ユースケース的なやつ
ライフタイム
- ライフタイムa(
'a
) - ライフタイムのスコープ
- グローバルなライフタイム(
'static
) - ユースケース的なやつ