Rustのメモ
構造体に文字列を持たせる場合、newの引数の型としては以下が良さそう。
参考文献: Rustの構造体に文字列を持たせるいくつかの方法
fn main() {
let bar = Foo::new("bar");
let baz = Foo::new("baz".to_string());
}
struct Foo {
string: String,
}
impl Foo {
fn new(string: impl Into<String>) -> Self {
Self {
string: string.into(),
}
}
}
文字数取得には.chars().count()
を使うと良さそう。
空文字判定には.is_empty()
を使う。
参考文献: Rustで文字列の長さ(文字数)を取得
fn main() {
let ascii = "Foo!";
let emoji = "😇🙌";
let grapheme = "🇯🇵🇺🇸";
assert_eq!(ascii.len(), 4); // バイト数を取得
assert_eq!(emoji.chars().count(), 2); // 書記素クラスタを考慮しない文字数取得
// required "unicode-segmentation" crate
use unicode_segmentation::UnicodeSegmentation;
assert_eq!(grapheme.graphemes(true).count(), 2); // 書記素クラスタを考慮した文字数取得
}
ライフタイムについての記事
Rustのライフタイムについてのよくある誤解
デバッグについての記事
Rust のデバッグチートシート
トレイトを用いて型パラメータに制約を与える以下のprint_field1,2,3の定義は全て同じ意味。
参考文献: Rust: ジェネリクスとトレイト
fn print_field1(foo: &impl GetTrait) {
println!("{}", foo.get_field());
}
fn print_field2<T: GetTrait>(foo: &T) {
println!("{}", foo.get_field());
}
fn print_field3<T>(foo: &T)
where T: GetTrait // 複数の場合はカンマ区切りで書く T: GetTrait, U: SetTrait
{
println!("{}", foo.get_field());
}
fn main() {
let foo = Foo {bar: 0};
print_field1(&foo);
print_field2(&foo);
print_field3(&foo);
}
struct Foo {
bar: i32
}
trait GetTrait {
fn get_field(&self) -> i32;
}
impl GetTrait for Foo {
fn get_field(&self) -> i32 {
self.bar
}
}
トレイトは型に対して実装できる。
struct Foo { f1: i32, f2: String } // フィールド型構造体
struct Bar(i32, String); // タプル型構造体
struct Baz; // ユニット型構造体
enum Qux { V1, V2 }
enum Quux {}
type Corge = fn() -> Option<i32>;
trait Grault {
fn get();
}
impl Grault for Foo {
fn get() { }
}
impl Grault for Bar {
fn get() { }
}
impl Grault for Baz {
fn get() { }
}
impl Grault for Qux {
fn get() { }
}
impl Grault for Quux {
fn get() { }
}
impl Grault for Corge {
fn get() { }
}
impl Grault for i32 {
fn get() { }
}
構造体は一部のフィールドのみ所有権を移動することもできる。
fn main() {
let foo = Foo { f1: "field1".to_string(), f2: 2 };
let bar = foo.f1;
println!("{}", bar);
// println!("{}", foo.f1); // 所有権移動済みのためエラー
let baz = foo.f2;
println!("{}", baz);
println!("{}", foo.f2); // Copyされたため有効
// println!("{:?}", foo); // 部分的に所有権移動済みのためエラー
}
#[derive(Debug)]
struct Foo {
f1: String,
f2: i32,
}
2重にforループを書く代わりにitertoolsが使える場合がある。
use itertools::Itertools;
fn main() {
let mut foo = Vec::new();
for i in 0..5 {
for j in 1..6 {
foo.push(i);
foo.push(j);
}
}
let mut bar = Vec::new();
for (i, j) in (0..5).cartesian_product(1..6) {
bar.push(i);
bar.push(j);
}
assert_eq!(foo, bar);
}
self
はある型のインスタンスを表現する。
Self
はある型を表現する。
ジャグ配列様のVecから一番要素数の多いVecのインデックスを特定する。
参考文献: Rustでmax_indexを取得する
fn main() {
let foo = vec![vec![1,2], vec![3,4,5,6], vec![7,8,9]];
let (index, _) = foo.iter()
.map(|x| x.len())
.enumerate()
.fold((usize::MIN, usize::MIN), |(acc_i, acc_l), (i, l)| if acc_l>l {(acc_i, acc_l)} else {(i, l)});
if let Some(x) = foo.get(index) {
println!("{:?}", x); // [3, 4, 5, 6]
}
}
演算子オーバーロードの例
// + -> Add::add
// += -> AddAssign::add_assign
// - -> Sub::sub
// * -> Mul::mul
// / -> Div::div
// % -> Rem::rem
fn main() {
let foo = Foo{ f1: 12, f2: 34 };
let bar = Bar{ f1: 56, f2: 78 };
println!("{:?}", foo + foo); // Foo { f1: 24, f2: 68 }
println!("{:?}", foo + bar); // Foo { f1: 90, f2: 90 }
}
#[derive(Clone, Copy, Debug)]
struct Foo { f1: i32, f2: i32 }
impl std::ops::Add<Foo> for Foo {
type Output = Foo;
fn add(self, rhs: Foo) -> Self::Output {
Self::Output{ f1: self.f1 + rhs.f1, f2: self.f2 + rhs.f2 }
}
}
impl std::ops::Add<Bar> for Foo {
type Output = Foo;
fn add(self, rhs: Bar) -> Self::Output {
Self::Output{ f1: self.f1 + rhs.f2, f2: self.f2 + rhs.f1 }
}
}
struct Bar { f1: i32, f2: i32 }
明示的なBox<T>の使い道
参考文献: 【Rust】Boxの使いどころ, RustのBoxの使い方【ヒープにメモリを確保!初心者向け】
- コンパイル時に確保が必要なメモリサイズが不明な場合
- 型定義の中で自身の型を持つとき
- トレイトオブジェクトを値のように扱うとき
- プリミティブ型で絶対にCopyしてほしくない場合
- メモリアドレスを固定する場合
簡単なiter可能かどうかの考え方
- 同じ型が連なる構造かどうか
- 可能: 配列やVec, Hashmap
- 不可能: タプルや構造体
文字列のアドレス
fn main() {
let a = "str";
let b = &a[..];
let c = &a[1..];
let d = a.to_string();
let e = &d;
let f = &b[..];
let g = &b[1..];
println!("{:p}", a); // static 's'のアドレス
println!("{:p}", b); // static 's'のアドレス
println!("{:p}", c); // static 't'のアドレス
println!("{:p}", d.as_ptr()); // heap struct Stringのアドレス
println!("{:p}", e); // stack Stringのアドレス
println!("{:p}", f); // heap Vec<u8>の第一要素アドレス
println!("{:p}", g); // heap Vec<u8>の第二要素アドレス
}
複合型(データ構造)の内容をただの変数に所有権を移動させる方法
fn main() {
let foo_int = Box::new(1);
let bar_int1 = Box::new(2);
let bar_int2 = Box::new(3);
let baz_int = Box::new(4);
let qux_int1 = Box::new(5);
let qux_int2 = Box::new(6);
let quux_int1 = Box::new(7);
let quux_int2 = Box::new(8);
// 用意したヒープの値を用いてデータ構造作成
let foo1 = foo_int;
let bar1 = vec![bar_int1, bar_int2];
let baz1 = Baz::V1(baz_int);
let qux1 = Qux { f1: qux_int1, f2: qux_int2 };
let quux1 = (quux_int1, quux_int2);
// 所有権がデータ構造に移動していることを確認 Error "value borrowed here after move"
// println!("{}", foo_int);
// println!("{}", bar_int1);
// println!("{}", bar_int2);
// println!("{}", baz_int);
// println!("{}", qux_int1);
// println!("{}", qux_int2);
// println!("{}", quux_int1);
// println!("{}", quux_int2);
// データ構造から内容の所有権をただの変数に取り戻す
let foo = foo1;
println!("last foo_int: {}", foo);
for (i, bar) in bar1.into_iter().enumerate() {
println!("last bar_int{}: {}", i+1, bar);
}
if let Baz::V1(baz) = baz1 {
println!("last baz_int: {}", baz);
}
let Qux {f1: qux, f2: _} = qux1; // 構造体の一部だけ所有権移動できる
println!("last qux_int1: {}", qux);
println!("last qux_int2: {}", qux1.f2);
let (quux, _) = quux1; // タプルも構造体と同じ
println!("last quux_int1: {}", quux);
println!("last quux_int2: {}", quux1.1);
// 所有権が移動していることを確認 Error "value borrowed here after move" 等
// println!("{}", foo1);
// println!("{:?}", bar1);
// println!("{:?}", baz1);
// println!("{}", qux1.f1);
// println!("{}", quux1.0);
}
#[derive(Debug)]
enum Baz { V1(Box<i32>) }
#[derive(Debug)]
struct Qux { f1: Box<i32>, f2: Box<i32> }
構造体のフィールドの値をCopy無しで入れ替える
std::mem::replace
の代わりにstd::mem::take
も使用可能(take
はdefault
と入れ替える)
fn main() {
let mut foo = Foo::new();
foo.replace_vec(); // ab
assert_eq!(foo.f1, vec!["c".to_string()]);
}
struct Foo {
f1: Vec<String>,
}
impl Foo {
fn new() -> Self {
Foo { f1: vec!["a".to_string(), "b".to_string()] }
}
fn replace_vec(&mut self) {
std::mem::replace(&mut self.f1, vec!["c".to_string()])
.into_iter()
.for_each(|x| print!("{}", x));
println!();
}
}
Option
とVec
のスマートな取り扱い例
fn main() {
let opt1: Option<i32> = None;
let opt2: Option<i32> = Some(9);
let mut nums: Vec<i32> = vec![1,2,3];
nums.extend(opt1);
nums.extend(opt2);
for n in nums {
print!("{}", n); // 1239
}
}
プライベートなクレート管理: privateなcrateの扱いについて
パターン記法色々
struct Foo { f1: i32, f2: i32 }
fn main() {
let foo = Foo { f1: 1, f2: 2 };
let bar = (3,4,5,6,7,8,9);
let baz = Some(3);
let qux: Result<String, String> = Ok("id2".to_string());
match foo {
Foo { f1: _x, f2: 3 } => unreachable!(),
Foo { f1: 0|1|2, f2: y } => assert_eq!(y, 2),
_ => unreachable!(),
}
match bar {
(0, .., _last) => unreachable!(),
(3, second, ..) => assert_eq!(second, 4),
_ => unreachable!(),
}
match baz {
Some(x) if x < 2 => unreachable!(),
Some(ref x @ 3..=7) => assert_eq!(*x, 3), // not take value
_ => unreachable!(),
}
if let Some(x @ 3) = baz { assert_eq!(x, 3); }
match qux {
Ok(x) if x == "id1" => { println!("match id1"); },
Ok(x) if x == "id2" => { println!("match id2"); },
Ok(x) if x == "id3" => { println!("match id3"); },
Ok(_) => { println!("Ok others"); },
_ => { println!("Error"); },
}
// match id2
}
ジェネリクスの文脈でPhantomDataを使用するときは、所有に関する処理や自動トレイトの制御を避けるためにPhantomData(T)
ではなくPhantomData<fn(T)>
を用いる。
参考文献: PhantomDataまとめ