Closed15

The Rust Programming Language - Chapter 8 メモ

Tatsushi KiryuTatsushi Kiryu

https://doc.rust-jp.rs/book-ja/ch08-01-vectors.html

ベクタで一連の値を保持する

ベクタは同じ型の値を保持するコレクション

// 空のベクタを生成、Vec<T> の型注釈が必要
let v: Vec<i32> = Vec::new();

// vec! により値を含むベクタを生成
let v = vec![1, 2, 3];

ベクタを更新する

rust
// push 可能にするには mut キーワードが必要
let mut v = vec![1, 2, 3];
v.push(4); // OK

let v = vec![1, 2, 3];
v.push(4); // NG
TypeScript
// ts は let でも const でも配列の要素の追加削除はできてしまう。
let v = [1, 2, 3];
v.push(4); // OK 

const v = [1, 2, 3];
v.push(4); // OK

ベクタをドロップすれば、要素もドロップする

  • ベクタがドロップされると、その中身もドロップされる。
    • Vec<T> の T が struct の場合どうなる??

ベクタの要素を読む

rust
let mut v = vec![1, 2, 3, 4, 5];

let third: &i32 = &v[2]; 
let ten: &i32 = &v[9]; // panic
// ベクタの範囲外にアクセスする可能性があるときは get を使う
let third: Option<&i32> = v.get(10); // None
TypeScript
const v = [1, 2, 3, 4, 5];
const third = v[2];
// out of range の要素にアクセスした場合は undefined が返るだけ
const ten = v[9] // undefined
  • 不変借用がある場合、可変で借用できない
Rust
let mut v = vec![1, 2, 3, 4, 5];
let first = &v[0];
v.push(6); // NG

新規要素をベクタの終端に追加すると、ベクタが現在存在する位置に隣り合って要素を入れるだけの領域がなかった場合に、 メモリの新規確保をして古い要素を新しいスペースにコピーする必要があるかもしれないからです。 その場合、最初の要素を指す参照は、解放されたメモリを指すことになるでしょう。借用規則により、 そのような場面に陥らないよう回避されるのです。

なるほど。

ベクタの値を走査する

Rust
let mut v = vec![100, 32, 57];
for i in &mut v {
    // 参照外し演算子(*)をつけないといけない
    *i += 50;
}
println!("{:?}", v); //  [150, 82, 107]
TypeScript
// TS でこう書いても配列の中身は変わらない
const v = [100, 32, 57];
for (const i of v) {
    i += 50;
}
console.log(v); // [100, 32, 57]

// やるならこう(Rust のほうが簡潔)
const v = [100, 32, 57];
for (let i = 0, len = v.length; i < len; i+) {
    v[i] += 50;
}
console.log(v); // [150, 82, 107]

Enum を使って複数の型を保持する

一応こういうこともできるよ、という話。

Rust
enum SpreadsheetCell {
    Int(i32),
    Float(f64),
    Text(String),
}
let row = vec![
    SpreadsheetCell::Int(3),
    SpreadsheetCell::Text(String::from("blue")),
    SpreadsheetCell::Float(10.12),
];
Tatsushi KiryuTatsushi Kiryu

文字列とは

  • Rust言語の核には文字列型は1種類しかない
    • str:文字列スライス
      • 別の場所に格納されたUTF-8エンコードされた文字列データへの参照
      • 文字列リテラルは、ブログラムのバイナリに出力されるので、文字列スライス
    • $str:通常借用された形態
  • 一方、String型は、Rustの標準ライブラリで提供される。
    • 伸長可能
    • 可変
    • 所有権のあるUTF-8エンコードされた文字列型
  • 文字列といえば、String&str を意味する。
  • Rustの標準ライブラリには、他の文字列型も存在する。
    • OsString
    • OsStr
    • CsString
    • CsStr
    • 末尾が String or Str で終わっているのは、前者が所有権あり、後者が借用されたバージョンを指す
      • String&str のようなもの
文字列型とString型は異なるもの
文字列といえば String と &str を意味する
Tatsushi KiryuTatsushi Kiryu

新規文字列を生成する

Rust
// &str    
let data = "initial contents";
// String型
let s = data.to_string();
let s = "initial contents".to_string();
let s = String::from("initial contents");

to_string()String::fromは同じこと。どちらを使うかはスタイル次第。

Tatsushi KiryuTatsushi Kiryu

文字列を更新する

Rust
let mut s1 = String::from("foo");
let s2 = "bar";
// s2は所有権を奪われない
s1.push_str(s2); 
println!("s2 is {}", s2); // bar
Tatsushi KiryuTatsushi Kiryu

+演算子、またはformat!マクロで連結

Rust
let s1 = String::from("Hello, ");
let s2 = String::from("world!");
// s1は所有権を奪われる
// s2は所有権を奪われない
// + 演算子を使用した時に add メソッドがポイント
let s3 = s1 + &s2;

// s1.add が s2 を &String  -> &str に型強制する
// self には & がついていないので s1 は add を読んだらムーブされる
fn add(self, s: &str) -> String {

format! マクロを使う

Rust
let s1 = String::from("tic");
let s2 = String::from("tac");
let s3 = String::from("toe");

// add では見づらいし、s1 はムーブされてしまう
let s = s1 + "-" + &s2 + "-" + &s3;
// format! を使えば、見やすく、かつs1もムーブされない
let s = format!("{}-{}-{}", s1, s2, s3);
Tatsushi KiryuTatsushi Kiryu

文字列に添字アクセスする

StringVec<u8> のラッパー。だけど添字アクセスは良くない!

Rust
// 期待通りになる
let hello = String::from("Hola")
let len = hello.len(); // 4
let first = &hello[0]; // H

// 期待通りにならない
let hello = String::from("Здравствуйте"); 
let len = hello.len(); // 12 ではなく 24!
let first = &hello[0]; // 3 ではない!
Tatsushi KiryuTatsushi Kiryu

文字列をスライスする

Rust
let hello = "Здравствуйте";
let s = &hello[0..4]; // Зд

// 範囲を使用して文字列スライスを作る際には、プログラムをクラッシュさせることがある
let s = &hello[0..1]; // panic
Tatsushi KiryuTatsushi Kiryu

文字列を走査するメソッド群

Rust
// char() を使えば、ちゃんと1文字ずつ処理することができる
for c in "नमस्ते".chars() {
    println!("{}", c);
}
Rust
// bytes() もうまくいくこともあるかもしれないが、Unicodeは2バイト以上からなることもあるので注意が必要
for b in "नमस्ते".bytes() {
    println!("{}", b);
}
Tatsushi KiryuTatsushi Kiryu

文字列はそう簡単じゃない

  • 文字列は込み入っている
  • RustではStringデータを正しく扱うことに注意が必要
    • UTF-8を素直に扱うより複雑
    • 一方で、ASCII位ギアの文字に対するエラーの可能性が排除されている
Tatsushi KiryuTatsushi Kiryu

https://doc.rust-jp.rs/book-ja/ch08-03-hash-maps.html

キーとそれに紐づいた値をハッシュマップに格納する

新規ハッシュマップを生成する

Rust
use std::collections::HashMap;

let mut scores = HashMap::new();
scores.insert(String::from("Blue"), 10);
scores.insert(String::from("Yellow"), 50);

// ベクターからハッシュマップへの変換
let teams = vec![String::from("Blue"), String::from("Yellow")];
let initial_scores = vec![10, 50];
// zip すると タプルのベクターができる
let teams_and_ teams.iter().zip(initial_scores.iter()); //  [("Blue", 10), ("Yellow", 50)]
// collect() を使って HashMap の型を明示してハッシュマップに変換する、型推論できるので _ でOK
let scores: HashMap<_, _> = teams_and_.collect();
Tatsushi KiryuTatsushi Kiryu

ハッシュマップと所有権

Rust
use std::collections::HashMap;

let field_name = String::from("Favorite color");
let field_value = String::from("Blue");

let mut map = HashMap::new();
// field_name も field_value もムーブされて、以降使えない
map.insert(field_name, field_value);
Tatsushi KiryuTatsushi Kiryu

ハッシュマップの値にアクセスする

Rust
use std::collections::HashMap;

let mut scores = HashMap::new();

scores.insert(String::from("Blue"), 10);
scores.insert(String::from("Yellow"), 50);

let team_name = String::from("Blue");
// get は Option<&v> を返す
let score = scores.get(&team_name); // 10 ではなく Some(10)

// for で走査する
for (key, value) in &scores {
    // Yellow: 50
    // Blue: 10
    println!("{}: {}", key, value); 
}
Tatsushi KiryuTatsushi Kiryu

ハッシュマップを更新する

値の上書き

Rust
let mut scores = HashMap::new();
scores.insert(String::from("Blue"), 10);
scores.insert(String::from("Blue"), 25);
// Blue というキーしかないので
println!("{:?}", scores); // {"Blue": 25}

キーに値がなかった時のみ値を挿入する

Rust
let mut scores = HashMap::new();
scores.insert(String::from("Blue"), 10);
// entry, or_insert で存在チェックし、なければ挿入する
scores.entry(String::from("Yellow")).or_insert(50);
scores.entry(String::from("Blue")).or_insert(50);
// Blue の値が 50 に上書きされていない
println!("{:?}", scores); // {"Blue": 10, "Yellow": 50}

古い値に基づいて値を更新する

Rust
let text = "hello world wonderful world";
let mut map = HashMap::new();
for word in text.split_whitespace() {
    // or_insert() は値への可変参照(&mut V)を返す
    // 最初はキーが存在していないので 0 がセットされ、その値を返す
    // 2回目以降すでにキーが存在している場合は、0ではなくセットされた値を返す
    let count = map.entry(word).or_insert(0);
    // or_insert() により可変参照が返るので、その値を更新できる
    *count += 1;
}
println!("{:?}", map); // {"world": 2, "hello": 1, "wonderful": 1}
このスクラップは2021/07/10にクローズされました