🎄teratailクリスマスプレゼントキャンペーン2024🎄』開催中!

\teratail特別グッズやAmazonギフトカード最大2,000円分が当たる!/

詳細はこちら
Rust

Rustは、MoFoが支援するプログラミング言語。高速性を維持しつつも、メモリ管理を安全に行うことが可能な言語です。同じコンパイル言語であるC言語やC++では困難だったマルチスレッドを実装しやすく、並行性という点においても優れています。

Q&A

解決済

2回答

5210閲覧

Rustの文字列の長さがi32やi64ではなくusizeである理由

TS01

総合スコア3

Rust

Rustは、MoFoが支援するプログラミング言語。高速性を維持しつつも、メモリ管理を安全に行うことが可能な言語です。同じコンパイル言語であるC言語やC++では困難だったマルチスレッドを実装しやすく、並行性という点においても優れています。

1グッド

2クリップ

投稿2020/12/18 20:34

Rustのlen()の戻り値がusizeの理由がわかりません。
i32やi64ではいけないのでしょうか?

該当のソースコード

Rust

1fn calculate_length(s: &String) -> i64 { 2 s.len() 3}
rize-ma👍を押しています

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

guest

回答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

これでコンパイルが通りました.

どこで lenusize を返すことを調べられるか

質問文に

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() のようにします.
ここで sString 型の値が束縛されている変数です.

註記:文字数の扱いに関して不正確な記述です.

なぜ std::string::Stringlenusize を返すのか

註記:この節では usize が多倍長整数であるかのような言い回しをしていますが,これは誤りです.

ここまで,Rust言語で「どのようになっているか」を見てきました.
しかし,「なぜそうなっているのか」を考えてはきませんでした.
ここからは質問に対する私の予想を書いています.

私が len を実装する立場にある状況を想像します.
一般に文字列の長さには制限がありません.
(したがって,文字列長を表す整数型に範囲の制限があると,面倒なことになりそうだと薄々気付いています.)

例えば, leni32 を返すものと決めたとします.

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_intousize を変換して, Result に包まれた i64 値を t に束縛しています.
その後, tErr の場合のみ(つまり 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 の返り値を i32u32u64 に変更することは,関数の中身の書き換えを求めません.
これも, lenusize を返すからですね.
そして, s のあらゆるケースに対応したコードを書くことができました.

また, unwrap は基本的に避けるべきであるということも述べたかったのです.
(もちろん例外はあります.例外だけに.)

もう全部あいつ (usize) 一人でいいんじゃないかな

残念ながら,良くないです.
もし,取り得る範囲がわかっているのであれば, i32 のような整数型を使うべきです.
範囲が決まっているということは,整数を表現するバイト数をコンパイル時に決めることができる,ということです.
これはパフォーマンスの向上に繋がります.
ただし,この辺りの話題については私の専門ではなく,また元の質問から離れつつあるため,コンパイラ関連の文献などを研究されますと幸甚です.

文字列以外の len

他にも Veclenusize を返します
なぜこのようになっているのか,想像を巡らせてみるのも一興かと存じます.

投稿2020/12/19 12:59

編集2020/12/21 12:23
gemmaro

総合スコア358

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

gemmaro

2020/12/19 13:02

補足:途中で `chars().count()` に言及したあとで `len` の場合を述べ続けていますが,`chars().count()` を使うべき場合はそちらを使うようにしてください.後半の議論は `chars().count()` にも敷衍できます.
TS01

2020/12/19 21:07

ご回答ありがとうございました。 丁寧な解説、とても参考になりました。 ドキュメントの索引方法についても、とても助かりました。
mosh

2020/12/21 11:02

重箱の隅をつついておくと`usize`に範囲の制限がないというのは多倍長整数を想起させて語弊のある表現だと思いました あと日常的に使われてる文字数の概念はcharじゃなくgrapheneのほうが近いと思います。そこまで踏み込むのは教育的でない気もしますが
gemmaro

2020/12/21 12:03

ご指摘ありがとうございます. * usizeは https://doc.rust-lang.org/std/primitive.usize.html で "For example, on a 32 bit target, this is 4 bytes and on a 64 bit target, this is 8 bytes." とあり,確かに多倍長整数ではありません.私が勘違いしていました. * grapheme については https://doc.rust-lang.org/book/ch08-02-strings.html#bytes-and-scalar-values-and-grapheme-clusters-oh-my を忘れていました.こちらも注意が必要ですね.
guest

0

  1. i32やi64の範囲に含まれる負数になる事がありえない
  2. コンパイルするアーキテクチャによって扱える桁数が可変にできる方が効率が良い

投稿2020/12/18 22:37

編集2020/12/18 22:42
gentaro

総合スコア8947

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

TS01

2020/12/19 07:48

回答ありがとうございました。 しかし、疑問点は残っています。 そもそもこのコードでコンパイルエラーになることが理解できません。 len()メソッドでは、負数になること自体が有り得ないように思うので、i32やi64も許容すべきではないのか、と思えてしまいます。。 加えて、実行するアーキテクチャによって扱える桁数を可にできる方が良いのは、全ての数値に言えることではないのでしょうか? 例えば let i: usize = 1; など、普段使う数値がusizeでないことと、文字列の長さを返却するメソッドの戻り値がusizeである理由が一致しないことに釈然としません。。 もしよろしければそのあたりも教えていただければと思います。
gentaro

2020/12/19 12:43

コンパイルエラーになることが理解できないのが理解できません。 静的型付け言語であればどんな言語でも型が違えばエラーになるのが自然で、そうでない場合というのは暗黙的な型変換が発生するケースだけです。 後段の疑問も使用する目的に応じて適切な型を使えばいいだけの話だと思いますが、何を疑問に思っているのか全くわかりません。
TS01

2020/12/24 16:17

ご回答ありがとうございました。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.36%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問