【Rust】がばがばRust独学 - 4. Ownership - 4 Slice Type
Rustの公式ドキュメントを通して、Rustを独学していく記事になります。(がば要素あり)
今回も、OwnershipのSlice Typeについて学びつつ、気になった点をコード確認した結果を記事にしています。
- versions
$ rustc --version rustc 1.40.0 (73528e339 2019-12-16)
SliceType
所有権を持たない別のデータ型をsliceと呼びます。
例えば、スペースが区切りで文字列の始めの1単語を持ってくるような関数を考えた場合、 下記のような関数が想定されます。
fn first_word(s: &String) -> ?
まずは、ドキュメント(The Slice Type - The Rust Programming Language)の demo codeと同様に末尾のindexを返すパターンを実行すると正常に動作します。
fn main() { let s = String::from("Hello Rust!!"); let fs = first_word(&s); println!("fs -> {}", fs); } fn first_word(s: &String) -> usize { let bytes = s.as_bytes(); for (i, &item) in bytes.iter().enumerate() { if item == b' ' { return i; } } s.len() }
fs -> 5
実装としては、文字列s
を let bytes = s.as_bytes();
によってbyte配列に変換後、
bytes.iter().enumerate()
によりバイト配列をiteratorとして反復させて、
スペースのバイト(b' '
)との比較でスペースが出てきたらその位置を返すような処理になっています。
下記は println!("item -> {}, space byte -> {}", item, b' ');
により、比較を確認するようにしたコードになります。
fn main() { let s = String::from("Hello Rust!!"); let fs = first_word(&s); println!("fs -> {}", fs); } fn first_word(s: &String) -> usize { let bytes = s.as_bytes(); for (i, &item) in bytes.iter().enumerate() { println!("item -> {}, space byte -> {}", item, b' '); if item == b' ' { return i; } } s.len() }
item -> 72, space byte -> 32 item -> 101, space byte -> 32 item -> 108, space byte -> 32 item -> 108, space byte -> 32 item -> 111, space byte -> 32 item -> 32, space byte -> 32 fs -> 5
しかし、demoコードでも記載されているように、対象文字列をclearした場合であっても、
文字列 s
と、初めのword位置 fs
までの位置は連動していないため、
エラーの原因となりやすく、脆弱になってしまいます。
fn main() { let mut s = String::from("Hello Rust!!"); let fs = first_word(&s); s.clear(); println!("fs -> {}", fs); } fn first_word(s: &String) -> usize { let bytes = s.as_bytes(); for (i, &item) in bytes.iter().enumerate() { if item == b' ' { return i; } } s.len() }
fs -> 5
上記の解決策として、 String
slice があります。
String
slice
文字列に対して &s[0..5]
や、0始まりのsliceの場合は &s[..5];
、文末までの場合は &s[5..];
のように記述してsliceすることが可能です。
Hello
をsliceによって切り出したものが下記になります。
fn main() { let s = String::from("Hello Rust!!"); let slice = &s[..5]; println!("slice -> {}", slice); }
slice -> Hello
上記の場合、ドキュメントにNOTEとして記載されておりますが、 マルチバイト文字の場合、途中で文字列sliceを作成しようとするとプログラムがエラーを起こして終了するとのことです。 そのため、ASCII以外のUTF-8による詳細な処理はドキュメントのChapter 8を参照するとのことでした。
Note: String slice range indices must occur at valid UTF-8 character boundaries. If you attempt to create a string slice in the middle of a multibyte character, your program will exit with an error. For the purposes of introducing string slices, we are assuming ASCII only in this section; a more thorough discussion of UTF-8 handling is in the “Storing UTF-8 Encoded Text with Strings” section of Chapter 8.
sliceを利用してfirst wordを取得する場合は下記のようになります。
fn main() { let s = String::from("Hello Rust!!"); let slice = first_word(&s); println!("s -> {}, slice -> {}", s, slice); } fn first_word(s: &String) -> &str { let bytes = s.as_bytes(); for (i, &item) in bytes.iter().enumerate() { if item == b' ' { return &s[0..i]; } } &s[..] }
s -> Hello Rust!!, slice -> Hello
&str
で参照している s
を消した場合(s.clear();
)は、
参照先が無くなるためエラーとなります。
fn main() { let mut s = String::from("Hello Rust!!"); let word = first_word(&s); s.clear(); println!("the first word is: {}", word); } fn first_word(s: &String) -> &str { let bytes = s.as_bytes(); for (i, &item) in bytes.iter().enumerate() { if item == b' ' { return &s[0..i]; } } &s[..] }
error[E0502]: cannot borrow `s` as mutable because it is also borrowed as immutable --> demo.rs:4:2 | 3 | let word = first_word(&s); | -- immutable borrow occurs here 4 | s.clear(); | ^^^^^^^^^ mutable borrow occurs here 5 | println!("the first word is: {}", word); | ---- immutable borrow later used here
上記のコードで、気になる点が2点あります。
str
とは何者?&str
のスコープが生きている
str
は何者?
str
の公式ドキュメント(std::str - Rust, str - Rust)を見ると、
let s = String::from("hello world");
とした場合の "hello world"
は &str
であり、Primitive Typeであるとのこと。
公式ドキュメント(The Slice Type - The Rust Programming Language)のFigure 4-6がわかりやすく、図の右側の表が str
であり、String
である s
が str
を参照することにより String
として利用でき、また、first_word
として返している &str
は図の world
部分にであり、参照を返している。
関数の戻り値で &str
が利用できる理由
上記の通り、 str
の実体が存在するため、ポインタとして &str
は返されるため利用ができる。
しかしながら、その場合、実体である str
のownershipはどのようの制御されるのだろうか・・・?
この疑問はまた別で確認していきます。
その他のslice
配列についても同様にsliceが可能です。
fn main() { let a = [1, 2, 3, 4, 5]; let slice = &a[1..3]; print!("a -> ["); for (_i, &item) in a.iter().enumerate() { print!("{}, ", item); } println!("]"); print!("slice -> ["); for (_i, &item) in slice.iter().enumerate() { print!("{}, ", item); } println!("]"); }
a -> [1, 2, 3, 4, 5, ] slice -> [2, 3, ]
終わりに
sliceによるポインタ周りの理解が深められたが、 str
の実体のownership周りが難しい・・・
教えてください、偉い人!