Rust exercisesとは
Rust exercisesとはgithub上で公開されているrustの練習用問題のようなやつです。rustの難しい概念などを一個ずつピックアップして実装することで理解を深めよう的な趣旨のやつです。
ただ、まとまった模範解答らしきやつは見当たらないので、自分でやってみた実装や自分なりの解釈をまとめてみようと思います。
実装的にはクリアできていてもよりよい書き方やrustらしい書き方があると思うので間違っているところ等ありましたらビシバシご指摘お願いいたします。。🙏
1. Hello world
to_string()
するかどうかぐらいの落とし穴しかなかったので割愛
2. 所有権に関するもの
ゴール1
push()
しているのにmut
で宣言されていないので、mut
で宣言するようにしておけばおk
let mut result = String::new();
ゴール2
そのままtext
を渡してしまうと所有権が関数に渡ってしまってそのあとにtext
が使えなくなってしまうので、ここでは一旦clone()
してコピーを渡すようにしました。これで所有権が失われることはありません。
let kanji_only = remove_hiragana(text.clone());
ゴール3
コピーせずに関数を実行するには関数が所有権を借用できるようにすればおkです(日本語が合っているか不安)。つまり参照渡しになるように関数を書き換えます。
fn remove_hiragana(text: &String) -> String {
// ...
}
これで呼び出し元で
let kanji_only = remove_hiragana(&text);
としてやればおkです。
ゴール4
これがちょっと出題者の意図が汲み取れずにどれが正解かわからない状況です。
// ゴール4:次の行をアンコメントしても実行できるようにしてください
output(message);
とあるので、
output(&message);
とするのはだめという風に解釈して、
let message = &format!("{}{}!", word_a, word_b);
output(message);
// ゴール4:次の行をアンコメントしても実行できるようにしてください
output(message);
fn output(text: &String) {
// ..
}
という風に実装してみました。ただ、&String
を変数で持つ意味がない的な話を聞いたのでoutput(&message);
で渡すか、1回目をclone()
してあげるだけで良さそうな気がするなという印象です。
3. 参照に関するもの
まずは、参照で渡せるようにしました。
fn main() {
let name = format!("dear rustaceans");
greet(&name);
greet(&name);
}
fn greet(name: &String) {
println!("Hello {}", name);
}
これで、参照を渡しているので実行できるようになります。
ゴール2
文字列を書き換えればいけるので割愛
ゴール3
スライスで関数に渡せるようにする必要があるので、greet()
を以下のように書き換えます。
fn greet(name: &str) {
println!("Hello {}", name);
}
そして呼び出し元で、
greet(&name[5..]);
としてやると想定通りに動きます。
ちなみに、
format!("dear rustaceans");
はString
ですが、greet()
では&str
で受けっとています。これはrustの型強制という仕様によるもので、これで暗黙の型変換が行われます。(&str
の指定がされて初めて&String
から&str
に変換される)
4. 変更可能な参照
ゴール1, 2, 3
let (mut word_a, word_b) = words();
// ..
fn concat(prefix: &mut String, postfix: &String) -> String {
prefix.push(' ');
for c in postfix.chars() {
prefix.push(c);
}
prefix.to_string()
}
このように書き換えました。&mut
で渡す変数はmut
で渡す必要があります。(mut
で渡す場合は所有権がわたるので何で宣言していても問題ない)
また、第一引数をそのままreturnしているので、参照を外す必要があります。to_string()
をすることで参照が外れます。(正確には中でコピーを作成して返してる感じな気がする)
5. Null返す可能性のある関数
ゴール1
unrwap()
したら終わり
ゴール2
unrwap()
だけだとNone
のときにpanicしてしまうので、パターンマッチでそれぞれの条件に対応してあげる必要があります。
fn show_price(name: &str, price: Option<&u32>) {
match price {
Some(value) => println!("{}は{}円です", name, value),
None => println!("{}は登録されていません", name)
}
}
このように実装してやることで、登録されていない場合もエラーを吐くことがなくなります。関数型の書き方なので慣れない人も多いかも?
6. エラーを返す可能性のある関数
ゴール1
unrwap()
がエラーの巣窟になっているので、またしてもパターンマッチで解決。
match read_file(&file) {
Ok(file) => println!("{}", file),
Err(err) => println!("no such file or directory")
}
このようにすると、エラーハンドリングができます。
ゴール2
fn read_file2(filename: &String) -> Result<String, String> {
File::open(filename)
.map_err(|err| err.to_string())
.and_then(|mut file| {
let mut contents = String::new();
file.read_to_string(&mut contents)
.map_err(|err| err.to_string())
.map(|_| contents)
})
}
コンビネーターを使ってResult
をうまく扱うようにしました。
and_then()
でResult
を結合したり、map()
でOk
のときだけの処理を書いたり、map_err()
でErr
のときだけの処理を書いています。
ただしこの書き方だとtry!
が使えなかったり、早期リターンができなかったりとデメリットもあるようです。個人的には関数型にそこまで慣れていないということもあり、手続き的に書く方が好きかなという印象です。