プログラミングRUST 第2版
1章 システムプログラマにもっといいものを
2章 Rustツアー
3章 基本的な型
3.5.3 raw ポインタ
-
*const T
へのキャストにより、参照型を不変な生ポインタへ変換できる -
*mut T
へのキャストにより、可変参照を可変な生ポインタへ変換できる
fn main() {
let x = 42;
let raw_pointer = &x as *const i32;
println!("x: {}", x);
println!("x at: {:p}", &x);
println!("x at: {:?}", raw_pointer);
let mut y = 42.;
let raw_mut_pointer = &mut y as *mut f64;
println!("y: {}", y);
println!("y at: {:p}", &y);
println!("y at: {:?}", raw_mut_pointer);
}
$ cargo run
...
x: 42
x at: 0x16bd26784
x at: 0x16bd26784
y: 42
y at: 0x16bd26858
y at: 0x16bd26858
3.6 配列、ベクタ、スライス
各型のメモリサイズを確認する。
use std::mem::size_of;
fn main() {
// 固定長配列
assert_eq!(size_of::<[i32; 5]>(), 20); // 4bytes(32bit) * 5
assert_eq!(size_of::<[f64; 10]>(), 80); // 8bytes(64bit) * 10
// ベクタ
assert_eq!(size_of::<Vec<i32>>(), 24); // 8bytes(pointer size) + 8bytes(capacity size) + 8bytes(length size)
// スライスの参照(ファットポインタ)
assert_eq!(size_of::<&[i32]>(), 16); // 8bytes(pointer size) + 8bytes(length size)
assert_eq!(size_of::<&[f64]>(), 16); // 8bytes(pointer size) + 8bytes(length size)
// 参照
assert_eq!(size_of::<&[i32; 5]>(), 8); // 8bytes(pointer size)
assert_eq!(size_of::<&[f64; 10]>(), 8); // 8bytes(pointer size)
assert_eq!(size_of::<&Vec<i32>>(), 8); // 8bytes(pointer size)
}
4章 所有権と移動
5章 参照
5.2.6 任意の式への参照の借用
節の主張とは逸れるが、数値演算子 +
が自動的に参照を解決している点に興味を惹かれた。
// rustc 1.75.0
fn main() {
let r: &i32 = &10;
let x1 = r + 5; // &i32 + i32
let x2 = *r + 5; // i32 + i32
let x3 = r + &5; // &i32 + &i32
let x4 = *r + &5; // i32 + &i32
assert_eq!(x1, 15);
assert_eq!(x2, 15);
assert_eq!(x3, 15);
assert_eq!(x4, 15);
}
静的解析ツールは、変数 x1
, x2
, x3
, x4
がいずれも i32
型であると推測した。
明示的に参照解決を行った i32
に同じ型の i32
を加える *r + 5
の結果、 x2
の型が i32
であることは直感的である。
疑問に感じるのは次の 2 点である。
-
&i32
に対して演算を行った結果であるx1
やx3
の型は&i32
ではないのだろうか -
i32
に&i32
を加えた結果であるx4
の型はなぜi32
なのだろうか
実際に演算子のオーバーロードを試してみることで、これらの疑問を解消することができた。
use std::ops;
#[derive(Clone, Copy, Debug)]
struct Myi32(i32);
impl ops::Add<Myi32> for Myi32 {
type Output = Myi32;
fn add(self, _rhs: Myi32) -> Myi32 {
Myi32(self.0 + _rhs.0)
}
}
fn main() {
let r = &Myi32(10);
let x2 = *r + Myi32(5);
assert_eq!(x2.0, 15);
}
この状態では、r + Myi32(5)
や r + &Myi32(5)
、*r + &Myi32(5)
はコンパイルエラーとなる。
let r = &Myi32(10);
let x1 = r + Myi32(5); // Error: cannot add `Myi32` to `&Myi32`
let x2 = *r + Myi32(5); // OK
let x3 = r + &Myi32(5); // Error: cannot add `&Myi32` to `&Myi32`
let x4 = *r + &Myi32(5); // Error: mismatched types; expected `Myi32`, found `&Myi32`
つまり、上述の疑問に自ら回答すると、答えは単純に i32
に対してそのようにシグネチャが定義されていたからである。実際のところ、直前の Myi32
のコンパイルエラーを解消したければ、Myi32
に対して、演算を行いたい型に対する Add
トレイトを実装すればよい。
use std::ops;
#[derive(Clone, Copy, Debug)]
struct Myi32(i32);
impl ops::Add<Myi32> for Myi32 {
type Output = Myi32;
fn add(self, _rhs: Myi32) -> Myi32 {
Myi32(self.0 + _rhs.0)
}
}
impl ops::Add<Myi32> for &Myi32 {
type Output = Myi32;
fn add(self, _rhs: Myi32) -> Myi32 {
Myi32(self.0 + _rhs.0)
}
}
impl ops::Add<&Myi32> for Myi32 {
type Output = Myi32;
fn add(self, _rhs: &Myi32) -> Myi32 {
Myi32(self.0 + _rhs.0)
}
}
impl ops::Add<&Myi32> for &Myi32 {
type Output = Myi32;
fn add(self, _rhs: &Myi32) -> Myi32 {
Myi32(self.0 + _rhs.0)
}
}
fn main() {
let r = &Myi32(10);
let x1 = r + Myi32(5); // &Myi32 + Myi32
let x2 = *r + Myi32(5); // Myi32 + Myi32
let x3 = r + &Myi32(5); // &Myi32 + &Myi32
let x4 = *r + &Myi32(5); // Myi32 + &Myi32
assert_eq!(x1.0, 15);
assert_eq!(x2.0, 15);
assert_eq!(x3.0, 15);
assert_eq!(x4.0, 15);
}
(前節で学んだように、.
演算子が参照を自動的に解決することに注意する必要がある。トレイトの実装の中で、&Myi32.0
は (*Myi32).0
と等価である)
5.3.1 ローカル変数の借用
- 参照は、参照先より長生きしない
- 参照の生存期間は、代入された変数の生存期間を包含する
6章 式
7章 エラー処理
7.2.4 エラーの伝播
?
演算子と unwrap()
メソッドの違いについてまとめる。
?
演算子も unwrap()
メソッドも Result<_>
型や Option<_>
型から値を取り出すという点では同じだが、取り出せなかった場合の挙動が異なる。?
演算子ではエラー処理を呼び出し元に移譲でき、unwrap()
メソッドではその場でパニックが起こる。
より具体的には、下記のコードで、
use std::io::{Error, ErrorKind};
fn strange_add(x: i32, y: i32) -> Result<i32, Error> {
if x + y != 42 {
Ok(x + y)
} else {
Err(Error::new(
ErrorKind::Other,
"The answer to the ultimate question of life, the universe and everything",
))
}
}
fn sub() -> Result<i32, Error> {
let z = strange_add(40, 2)?; // or strange_add(40, 2).unwrap();
Ok(z)
}
fn main() {
match sub() {
Ok(result) => println!("The result is: {}", result),
Err(err) => eprintln!("Error: {}", err),
}
}
?
では std::io::Error
が呼び出し元の main
関数に返り、そこで処理が行われる一方で、unwrap()
では sub
関数の中でパニックが発生する。
8章 クレートとモジュール
9章 構造体
9.11 内部可変性
std::cell::RefCell
により、mutable でない MyStruct
の一部のフィールドに mut
でアクセスできる。
use std::cell;
struct Logger;
impl Logger {
fn ref_mut_method(&mut self) {}
}
struct MyStruct {
logger: cell::RefCell<Logger>,
other_immutable_field: i32,
}
fn main() {
let my_struct = MyStruct {
logger: cell::RefCell::new(Logger),
other_immutable_field: 42,
};
my_struct.logger.borrow_mut().ref_mut_method();
}
std::cell::RefCell
を利用しない場合、MyStruct
を mut
宣言する必要がある。
struct Logger;
impl Logger {
fn ref_mut_method(&mut self) {}
}
struct MyStruct {
logger: Logger,
other_immutable_field: i32,
}
fn main() {
let mut my_struct = MyStruct {
logger: Logger,
other_immutable_field: 42,
};
my_struct.logger.ref_mut_method();
}
10章 列挙型とパターン
11章 トレイトとジェネリクス
12章 演算子オーバーロード
13章 ユーティリティトレイト
13.3 Clone, 13.4 Copy
-
Clone
トレイトを実装した型は、.clone()
あるいは.clone_from()
メソッドによって値のコピーを作成することができる#[derive(Clone)] struct Myi32(i32); fn main() { let v1 = Myi32(42); let v2 = v1.clone(); assert!(v1.0 == v2.0); // OK }
-
Copy
トレイトを実装した型は、代入による変数の移動が起こらないstruct Myi32(i32); fn main() { let v1 = Myi32(42); let v2 = v1; assert!(v1.0 == v2.0); // Error: use of moved value: `v1`; value used here after move }
#[derive(Clone, Copy)] struct Myi32(i32); fn main() { let v1 = Myi32(42); let v2 = v1; // 暗黙的に v1.clone() が代入される assert!(v1.0 == v2.0); // OK }