Rustのlen()の戻り値がusizeの理由がわかりません。
i32やi64ではいけないのでしょうか?
該当のソースコード
Rust
1fn calculate_length(s: &String) -> i64 { 2 s.len() 3}
気になる質問をクリップする
クリップした質問は、後からいつでもMYページで確認できます。
またクリップした質問に回答があった際、通知やメールを受け取ることができます。
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
回答2件
0
ベストアンサー
註記:この回答には何点か誤りを含む記述があります(本文の修正は加えていません).本文中の「註記」箇所について,詳しくはコメント欄を参照してください.
コンパイルが通るまで
まず,問題文のコードについて見てみます.
rust
1fn main() { 2 let s = "This is a string.".to_string(); 3 calculate_length(&s); 4} 5 6fn calculate_length(s: &String) -> i64 { 7 s.len() 8}
例えばこういった内容に補完してコンパイルすると,確かにコンパイルエラーになります.
shell
1~/workspace/sandbox/teratail-311153 2$ cargo build 3 Compiling teratail-311153 v0.1.0 (/Users/gemmaro/workspace/sandbox/teratail-311153) 4error[E0308]: mismatched types 5 --> src/main.rs:7:5 6 | 76 | fn calculate_length(s: &String) -> i64 { 8 | --- expected `i64` because of return type 97 | s.len() 10 | ^^^^^^^ expected `i64`, found `usize` 11 | 12help: you can convert an `usize` to `i64` and panic if the converted value wouldn't fit 13 | 147 | s.len().try_into().unwrap() 15 | 16 17error: aborting due to previous error 18 19For more information about this error, try `rustc --explain E0308`. 20error: could not compile `teratail-311153` 21 22To learn more, run the command again with --verbose.
s.len()
の部分はusize
型calculate_length
の返り値の型はi64
なので矛盾しているのですね.
help
にあるように修正してみるとこうなります.
rust
1fn main() { 2 let s = "This is a string.".to_string(); 3 calculate_length(&s); 4} 5 6fn calculate_length(s: &String) -> i64 { 7 s.len().try_into().unwrap() 8}
再度コンパイルしてみましょう.
shell
1~/workspace/sandbox/teratail-311153 2$ cargo build 3 Compiling teratail-311153 v0.1.0 (/Users/gemmaro/workspace/sandbox/teratail-311153) 4error[E0599]: no method named `try_into` found for type `usize` in the current scope 5 --> src/main.rs:7:13 6 | 77 | s.len().try_into().unwrap() 8 | ^^^^^^^^ method not found in `usize` 9 | 10 = help: items from traits can only be used if the trait is in scope 11help: the following trait is implemented but not in scope; perhaps add a `use` for it: 12 | 131 | use std::convert::TryInto; 14 | 15 16error: aborting due to previous error 17 18For more information about this error, try `rustc --explain E0599`. 19error: could not compile `teratail-311153` 20 21To learn more, run the command again with --verbose.
今度は別のエラーが出ました. help
によると, usize
が実装している std::convert::TryInto
トレイトから try_into()
が提供されているけれども,そのトレイトが宣言されていません. TryInto
を使うことを宣言しましょう.
rust
1use std::convert::TryInto; 2 3fn main() { 4 let s = "This is a string.".to_string(); 5 calculate_length(&s); 6} 7 8fn calculate_length(s: &String) -> i64 { 9 s.len().try_into().unwrap() 10}
shell
1~/workspace/sandbox/teratail-311153 2$ cargo build 3 Compiling teratail-311153 v0.1.0 (/Users/gemmaro/workspace/sandbox/teratail-311153) 4 Finished dev [unoptimized + debuginfo] target(s) in 1.59s
これでコンパイルが通りました.
どこで len
が usize
を返すことを調べられるか
質問文に
Rustの
len()
の戻り値がusize
の理由がわかりません。
とありますが,Rust の全ての len()
が usize
を返すとは限りません.
ここでは String
型の値について value.len()
するときを考えます.
Rust の標準ライブラリのドキュメントはオンラインでも読めますし,もし rustup
がインストールされているパソコンであれば, $ rustup doc --std
としてもドキュメントのページを開くことができます.
String
を調べてみましょう.
左の "Methods" の一覧の中に len
がありますので選択します.
pub fn len(&self) -> usize
とありますが,これが len()
の型シグネチャです.
返り値が usize
であることはこのようにして調べることができます.
ところで,
Returns the length of this String, in bytes, not
chars
or graphemes. In other words, it may not be what a human considers the length of the string.
とありますね.
注意すべき点なのですが,「日常的に使われる意味での文字列長」を得るときには, s.len()
は使えないのです.
例にも示されている通り, s.chars().count()
のようにします.
ここで s
は String
型の値が束縛されている変数です.
註記:文字数の扱いに関して不正確な記述です.
なぜ std::string::String
の len
は usize
を返すのか
註記:この節では usize
が多倍長整数であるかのような言い回しをしていますが,これは誤りです.
ここまで,Rust言語で「どのようになっているか」を見てきました.
しかし,「なぜそうなっているのか」を考えてはきませんでした.
ここからは質問に対する私の予想を書いています.
私が len
を実装する立場にある状況を想像します.
一般に文字列の長さには制限がありません.
(したがって,文字列長を表す整数型に範囲の制限があると,面倒なことになりそうだと薄々気付いています.)
例えば, len
が i32
を返すものと決めたとします.
rust
1pub fn len(&self) -> i32
i32
にも上限があり,その数はたったの (?) 2147483647
です.
あるとき,文字列長 2147483648 ( = 2147483647 + 1 )
の文字列が与えられたとしましょう.
このとき, len
は何を返すべきでしょうか.
rust
1let t: i32 = s.len(); //=> ???
表現できる整数型の範囲を超えてしまっているので,長すぎる文字列に対しては実行時にパニックを起こすようにしましょうか.
でも,それだと安心してプログラミングできませんよね.
このような「失敗しうる計算」については Option
型(もしくは Result
型)を返すようにするのも手です.
さきほどの型シグネチャを変更して次のようにしてみます.
rust
1pub fn len(&self) -> Option<i32>
これはまだありそうな感じがします.
実際に使うときはこんな感じになります.
rust
1let t: Option<i32> = s.len(); 2 3match t { 4 Some(u) => { 5 // t を使うなんらかの処理 6 } 7 None => { 8 // s の文字列長が大きすぎたときの処理 9 } 10}
よさそうです.
しかし,「文字列長が i32
の上限超え i64
の上限以下の文字列」の文字列長を扱いたくなるときがきたらどうしましょうか.
先程実装した len
だと, i32
の上限を超えるものについては一律 None
を返すことにしていて使えません.
それでは, Option<i64>
を返すように変更しましょうか.
でも, i64
の上限を超えたときはどうしましょう.
実は i128
があって……
将来的にコンピュータの仕様が激変して i256
, i512
, ... が出てきたらどうしましょうか.いちいち返り値の型を変更しなくてはいけないのでしょうか.
どうしても溢れてしまう計算結果は,諦めるよりないのでしょうか.
ここまでの想像をもとに考えると,範囲に制限のない usize
みたいな整数型があれば,それを返したほうがよさそうです.
そして, usize
はもちろん実在します.
usize
を返すように設計したくなりませんか?
よりよいコードを考える
冒頭でコンパイルを通すまでを辿りましたが,実はあれで完成ではありません.再掲します.
rust
1use std::convert::TryInto; 2 3fn main() { 4 let s = "This is a string.".to_string(); 5 calculate_length(&s); 6} 7 8fn calculate_length(s: &String) -> i64 { 9 s.len().try_into().unwrap() 10}
calculate_length
中で unwrap
を使っている点が気になります.
このままだと, s
によってはパニックしてしまいます.
あまり現実的ではないですが,例えば i64
の上限を超える文字列長の s
が与えられたら,一律で 42
を返すような挙動であるとします.
それは,次のように書くことができます.
rust
1use std::convert::TryInto; 2 3fn main() { 4 let s = "This is a string.".to_string(); 5 calculate_length(&s); 6} 7 8fn calculate_length(s: &String) -> i64 { 9 let t: Result<i64, _> = s.len().try_into(); 10 11 t.unwrap_or(42) 12}
try_into
で usize
を変換して, Result
に包まれた i64
値を t
に束縛しています.
その後, t
が Err
の場合のみ(つまり i64
への変換が失敗するとき) 42
として返しています.
型推論があるので,少し簡単にします.
rust
1use std::convert::TryInto; 2 3fn main() { 4 let s = "This is a string.".to_string(); 5 calculate_length(&s); 6} 7 8fn calculate_length(s: &String) -> i64 { 9 let t = s.len().try_into(); 10 11 t.unwrap_or(42) 12}
ここで calculate_length
の返り値を i32
や u32
や u64
に変更することは,関数の中身の書き換えを求めません.
これも, len
が usize
を返すからですね.
そして, s
のあらゆるケースに対応したコードを書くことができました.
また, unwrap
は基本的に避けるべきであるということも述べたかったのです.
(もちろん例外はあります.例外だけに.)
もう全部あいつ (usize
) 一人でいいんじゃないかな
残念ながら,良くないです.
もし,取り得る範囲がわかっているのであれば, i32
のような整数型を使うべきです.
範囲が決まっているということは,整数を表現するバイト数をコンパイル時に決めることができる,ということです.
これはパフォーマンスの向上に繋がります.
ただし,この辺りの話題については私の専門ではなく,また元の質問から離れつつあるため,コンパイラ関連の文献などを研究されますと幸甚です.
文字列以外の len
他にも Vec
の len
も usize
を返します.
なぜこのようになっているのか,想像を巡らせてみるのも一興かと存じます.
投稿2020/12/19 12:59
編集2020/12/21 12:23総合スコア358
0
- i32やi64の範囲に含まれる負数になる事がありえない
- コンパイルするアーキテクチャによって扱える桁数が可変にできる方が効率が良い
投稿2020/12/18 22:37
編集2020/12/18 22:42総合スコア8947
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
2020/12/19 12:43
2020/12/24 16:17
あなたの回答
tips
太字
斜体
打ち消し線
見出し
引用テキストの挿入
コードの挿入
リンクの挿入
リストの挿入
番号リストの挿入
表の挿入
水平線の挿入
プレビュー
質問の解決につながる回答をしましょう。 サンプルコードなど、より具体的な説明があると質問者の理解の助けになります。 また、読む側のことを考えた、分かりやすい文章を心がけましょう。
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
2020/12/19 13:02
2020/12/19 21:07
2020/12/21 11:02
2020/12/21 12:03