https://doc.rust-lang.org/book/second-edition/
let で変数定義 immutable
let mut で mutable
const で定数定義。実行時に計算するような値は入れられない。コードのあちこちで使うような値を入れる
let x = 5;
let x = x + 1;
↑ シャドーイング。仮に let x = 5; x = x + 1; と書いたらイミュータブルだから怒られる
i8, i16, i32, i64 で signed な数
u8 - 64 で unsigned
isize, usize はアーキテクチャ任せ
tuple は let x: (i32, f64, u8) = (50, 3.2, 8); みたいに定義
x.0, x.1, x.2 でアクセス
let (a, b, c) = x; でスプレッド
配列は let a = [1,2,3];
let x = String::from("hoge");
let y = x;
println!("{}", x);
このコードはエラーになる。
shallow とか deep とかとも違い move といって、所有権が移動する
単純に let y = x.clone(); とすればうごく
let x = 1;
let y = x;
println!("{}", x);
このコードは動く。
Copy trait を持ってる型なら勝手にコピーしてくれる。
「なんとなく shallow copy とか deep copy にはならなそうだな」と思ったらでいけそう
fn main() {
let s = String::from("hello");
takes_ownership(s); // 以降、このスコープで s は使えない
let x = 5;
makes_copy(x); // x は Copy できるので所有権が移動しない
}
fn main() {
let s1 = gives_ownership(); // 戻り値の所有権をもらう
let s2 = String::from("hello");
let s3 = takes_and_gives_back(s2); // s2 の所有権を渡して戻り値の所有権をもらう
}
fn gives_ownership() -> String {
let some_string = String::from("hello");
some_string
}
fn takes_and_gives_back(a_string: String) -> String {
a_string
}
まぁまぁ普通にかける感じはするけど、流石に「一度渡したら以降使えない」だけだとつらいので、 4.2 の参照がある
let s1 = String::from("hello")
len = calc(s1)
println!("{}", s1) // コンパイルエラー
fn calc(s: String) -> usize {
s.len()
}
こうしちゃうと println でエラー。
所有権が calc にうつっちゃうのでもとのところでは使えない。
rust では & つけて参照渡しをすると、所有権は渡さずに値を借りる (borrowing) ことができて、 function を抜けたときに自動で返す挙動になる。
ただし、借りてきた値が immutable だと、破壊的な操作は実行できない
mutable な操作をしたいならこう。
fn main() {
let mut s = String::from("hello");
change(&mut s);
}
fn change(some_string: &mut String) {
some_string.push_str(", world");
}
ちゃんと最初の定義から let mut でやって、 mutable な reference で書いていく
mutable reference には大きな制約があり、同じスコープ内に別の mutable/immutable reference を持つことができない。
let mut s = String::from("hello");
let r1 = &mut s;
let r2 = &mut s; // コンパイルエラーになる
let mut s = String::from("hello");
let r1 = &s;
let r2 = &s; // 問題なし
let r3 = &mut s; // コンパイルエラーになる
なぜ: r1 で読んで r2 で書いてをマルチスレッドでやったら data race 起きるじゃん
中括弧でスコープ分けてやればエラーにならない
rust は Dangling References (宙ぶらりんな参照) は作れないようになっている
fn main() {
let reference_to_nothing = dangle();
}
fn dangle() -> &String {
let s = String::from("hello");
&s // コンパイルエラー
}
s は dangle を抜けたらメモリ上から開放されるので、 &s は dangling reference になってしまう。が、 rust はこれがコンパイルエラーになる。
この例だと参照渡しせずに直接渡すのが正解。所有権が移動するので、 s が開放されずにすむ
まぁでも rust の旨味として、 mutable はあんまり使わないほうがいいんだろうなと思う
文字列の一部を切り出すのに slice 使う例が書かれてるんだけど、これ 2 バイト文字とかどうなるのって思ったら案の定使えないらしい。
いろいろ調べてたら下記の記事が参考になりそうだった。ともかく文字列操作は一筋縄ではいかないもよう
https://qiita.com/aflc/items/f2be832f9612064b12c6
ここでは immutable 保ったまま配列の一部を切り出すには slice つかうといいよくらいでよさそう
#![allow(unused_variables)]
fn main() {
let a = [1, 2, 3, 4, 5];
let slice = &a[1..3];
}
構造体
普通な感じ
struct User {
username: String,
email: String,
sign_in_count: u64,
active: bool,
}
こんなふうに作る ↓
let user1 = User {
email: String::from("[email protected]"),
username: String::from("someusername123"),
active: true,
sign_in_count: 1,
};
mutable にすれば再代入もできる ↓
特定のフィールドだけを mutable にしたりはできない。
これやるよりはあとから出てくる ..
使って新しいインスタンス作ったほうがよさそう。
let mut user1 = User {
email: String::from("[email protected]"),
username: String::from("someusername123"),
active: true,
sign_in_count: 1,
};
user1.email = String::from("[email protected]");
構造体を作る関数 ↓
ES6 みたいに同じフィールドに同じ変数名で値をセットするときは省略できる
fn build_user(email: String, username: String) -> User {
User {
email: email,
username: username,
active: true,
sign_in_count: 1,
}
}
fn build_user(email: String, username: String) -> User {
User {
email,
username,
active: true,
sign_in_count: 1,
}
}
struct update syntax というので、別のインスタンスのフィールドを使って新しいインスタンスをつくれる。これまた ES6 っぽい。挙動も一緒で、下記だと email, username 以外は user1 の値が使われる
let user2 = User {
email: String::from("[email protected]"),
username: String::from("anotherusername567"),
..user1
};
tuple も名前をつけられる (Tuple structs) ↓
struct Color(i32, i32, i32);
struct Point(i32, i32, i32);
let black = Color(0, 0, 0);
let origin = Point(0, 0, 0);
ここは一通り読んで書いてみたほうが良さそう。
構造体の具体的な使い方と、アノテーションとかが出てくる
構造体へのメソッド追加
impl Rectangle {
fn area(&self) -> u32 {
self.width * self.height
}
}
&self を書かなければ static メソッド(static という言い方はしてないので、誤解あるかも)
impl Rectangle {
fn square(size: u32) -> Rectangle {
Rectangle { width: size, height: size }
}
}
impl は分けてかける
impl Rectangle {
fn area(&self) -> u32 {
self.width * self.height
}
}
impl Rectangle {
fn can_hold(&self, other: &Rectangle) -> bool {
self.width > other.width && self.height > other.height
}
}
定義は普通
enum IpAddrKind {
V4,
V6,
}
引数にとったり呼び出しも普通
fn route(ip_type: IpAddrKind) {}
let four = IpAddrKind::V4;
let six = IpAddrKind::V6;
それぞれに違う struct にすることが可能
enum Message {
Quit,
Move { x: i32, y: i32 },
Write(String),
ChangeColor(i32, i32, i32),
}
インスタンスメソッドをもたせたりもできる
impl Message {
fn call(&self) {
}
}
m.call();
他の言語でもよくある Optional だけど、 rust の場合は下記のような enum で実装されてる。
enum Option<T> {
Some(T),
None,
}
先の enum の例を踏まえると下記のようにかける
let some_number = Some(5);
let absent_number: Option<i32> = None;
なるほど
パターンマッチはこんな感じ。単に値を返すだけじゃなくて処理を書いたり、値を扱ったり
enum Coin {
Penny,
Nickel,
Dime,
Quarter(UsState),
}
#[derive(Debug)]
enum UsState {
Alabama,
Alaska,
}
fn value_in_cents(coin: Coin) -> u32 {
match coin {
Coin::Penny => {
println!("Lucky penny!");
1
},
Coin::Nickel => 5,
Coin::Dime => 10,
Coin::Quarter(state) => {
println!("{:?}", state);
25
}
}
}
fn main() {
println!("{}", value_in_cents(Coin::Penny));
println!("{}", value_in_cents(Coin::Quarter(UsState::Alabama)));
}
Option を match であつかうとこんな感じ ↓
fn plus_one(x: Option<i32>) -> Option<i32> {
match x {
None => None,
Some(i) => Some(i + 1),
}
}
すべてのパターンを網羅してないとコンパイルエラーになる。
例えば上記の None => None を削るとエラー。
ほか全て、は _ placeholder で受けることができる。 ()
は何もしない
match some_u8_value {
1 => println!("one"),
3 => println!("three"),
5 => println!("five"),
7 => println!("seven"),
_ => (),
}
1 こだけマッチさせたいパターンだと match では冗長なときもあるので、そのときは if let も使う。
下記の例は else if も使っていて、これなら match を使ったほうが良いだろう
fn plus_one(x: Option<i32>) -> Option<i32> {
if let Some(5) = x {
Some(100)
} else if let Some(i) = x {
Some(i + 1)
} else {
None
}
}
おそらく Option で使うことが多そう。
下記のように新しいプロジェクトを作ってからスタート。
lib についての詳しい説明はまたそのうち出てくるはず。
$ cargo new communicator --lib
$ cd communicator
ざっくりいうとディレクトリ構成の通りにモジュールのネームスペースが切られるよって話がメイン
└── src
├── client.rs
├── lib.rs
└── network
├── mod.rs
└── server.rs
// client.rs
fn connect() {
}
// network/mod.rs
fn connect() {
}
mod server;
// network/server.s
fn connect() {
}
こういう構成のディレクトリにすると、
// lib.rs
mod client;
mod network;
と、こんな具合に呼び出せる。これは実質下記と一緒
mod client {
fn connect() {
}
}
mod network {
fn connect() {
}
mod server {
fn connect() {
}
}
}
上記のコードを呼び出してみる
// src/main.rs
extern crate communicator;
fn main() {
communicator::client::connect();
}
extern crate
でクレートを呼び出す。
これは main.rs か lib.rs にしか書くことができない。
main.rs, lib.rs は root モジュールと呼ぶ。
この main.rs ファイルを作った時点で、 communicator パッケージは binary crate と library crate の 2 つのクレートを持つことになる。
これは実行可能なプロジェクトでは一般的な構成で、うまく関心を分離(separation of concerns)できる。
これを書いただけだと下記のようなエラーになる。
error[E0603]: module `client` is private
--> src/main.rs:4:5
|
4 | communicator::client::connect();
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
要は client モジュールは private だから外部クレートから呼べないよというエラー。
rust はデフォルト private で、 pub をつけると public になる。
// lib.rs
pub mod client;
mod network;
// client.rs
pub fn connect() {
}
このようにモジュールと関数どちらにも pub をつけられる。