LoginSignup
4
2

More than 3 years have passed since last update.

Rust exercisesやってみた

Last updated at Posted at 2019-08-24

Rust exercisesとは

Rust exercisesとはgithub上で公開されているrustの練習用問題のようなやつです。rustの難しい概念などを一個ずつピックアップして実装することで理解を深めよう的な趣旨のやつです。
ただ、まとまった模範解答らしきやつは見当たらないので、自分でやってみた実装や自分なりの解釈をまとめてみようと思います。

実装的にはクリアできていてもよりよい書き方やrustらしい書き方があると思うので間違っているところ等ありましたらビシバシご指摘お願いいたします。。🙏

1. Hello world

to_string()するかどうかぐらいの落とし穴しかなかったので割愛

1のdiff

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()してあげるだけで良さそうな気がするなという印象です。

2のdiff

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に変換される)

3のdiff

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()をすることで参照が外れます。(正確には中でコピーを作成して返してる感じな気がする)

4のdiff

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)
    }
}

このように実装してやることで、登録されていない場合もエラーを吐くことがなくなります。関数型の書き方なので慣れない人も多いかも?

5のdiff

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!が使えなかったり、早期リターンができなかったりとデメリットもあるようです。個人的には関数型にそこまで慣れていないということもあり、手続き的に書く方が好きかなという印象です。

6のdiff

4
2
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
2