Rustlings 新規へ
Rustlingsの解答を書いていきます。
12.3 Option型と借用
問題
#[derive(Debug)]
struct Point {
x: i32,
y: i32,
}
fn main() {
let optional_point = Some(Point { x: 100, y: 200 });
// TODO: Fix the compiler error by adding something to this match statement.
match optional_point {
Some(p) => println!("Co-ordinates are {},{}", p.x, p.y),
_ => panic!("No match!"),
}
println!("{optional_point:?}"); // Don't change this line.
}
これに対する解答として以下の2通りがある。
-
11行目の
optional_pointを&optional_pointにするmatch &optional_point {
Some(p) => println!("Co-ordinates are {},{}", p.x, p.y),
_ => panic!("No match!"),
} -
12行目の
Some(p)をSome(ref p)にするmatch optional_point {
Some(ref p) => println!("Co-ordinates are {},{}", p.x, p.y),
_ => panic!("No match!"),
}
この場合&とrefの違いは何か?
解説
&とrefの違い(refとは何なのか )
&xはxの参照を表す演算子だが、逆にref xはxをmoveしない様にコンパイラに指示する演算子である。結果としてref xはxの参照を作成する。
matchでは受け取る値をmoveしようとするため、そのSomeの中身もmoveしようとする。下記はStackoverflowにある例である。
let robot_name = &Some(String::from("Bors"));
match robot_name {
Some(name) => println!("Found a name: {}", name),
None => (),
}
println!("robot_name is: {:?}", robot_name);
この場合、robot_nameは&SomeであるためSome(name)ではマッチできず、&Some(name)とする必要がある。
さらに、&Someの中身のnameをコンパイラはmoveしようとするが、Someが参照のためmoveできない。そのためrefを使ってSome(ref name)とすることでmoveしないように指示する。
let robot_name = &Some(String::from("Bors"));
match robot_name {
&Some(ref name) => println!("Found a name: {}", name),
None => (),
}
println!("robot_name is: {:?}", robot_name);
こうすることでrobot_nameは&Someのままであり、nameも&strとなりmoveしない。
13.4 Ordering (Enum)
エラー処理の問題だが、Orderingの使い方の勉強になる。
問題
下記のnew関数が入力値に応じて正しいCreationErrorのバリアントを返すように修正する問題。
#[derive(PartialEq, Debug)]
enum CreationError {
Negative,
Zero,
}
#[derive(PartialEq, Debug)]
struct PositiveNonzeroInteger(u64);
impl PositiveNonzeroInteger {
fn new(value: i64) -> Result<Self, CreationError> {
// TODO: This function shouldn't always return an `Ok`.
Ok(Self(value as u64))
}
}
解説
Ordering Enumを使わない場合
ifでの比較をパターンマッチと合わせて行う場合の例。この場合matchを利用する必要も特に無い。
impl PositiveNonzeroInteger {
fn new(value: i64) -> Result<Self, CreationError> {
// TODO: This function shouldn't always return an `Ok`.
match value {
x if x < 0 => Err(CreationError::Negative),
0 => Err(CreationError::Zero),
x => Ok(PositiveNonzeroInteger(x as u64)),
}
}
}
Ordering Enumを使う場合
数値型にはcmpというメソッドがあり、これを使うと綺麗に描ける。
Rust的に言うと、
cmpはOrdトレイトを実装している型に対して使えるメソッドである。Ordトレイトを実装していることでその構造体が比較可能であることを示している。
impl PositiveNonzeroInteger {
fn new(value: i64) -> Result<Self, CreationError> {
// TODO: This function shouldn't always return an `Ok`.
match value.cmp(&0) {
Ordering::Less => Err(CreationError::Negative),
Ordering::Equal => Err(CreationError::Zero),
Ordering::Greater => Ok(PositiveNonzeroInteger(value as u64)),
}
}
}
この場合、value.cmp(&0)でOrdering Enumを返し、それのバリアント(Less, Equal, Greater)によって処理を分岐している。
17.3 should_panic
panicを起こすべきケースをテストする際のtips。
問題
下記のnewメソッドはパニックを起こすが、それをテストするためのテストコードを書く。
// TODO: This test should check if the program panics when we try to create
// a rectangle with negative width.
#[test]
fn negative_width() {
let _rect = Rectangle::new(-10, 10);
}
解説
should_panicの使い方
should_panicはテストがパニックすることを確認するためのマクロである。下記のように使う。
#[test]
#[should_panic]
fn negative_width() {
let _rect = Rectangle::new(-10, 10);
}
これだけで、Rectangle::new(-10, 10)がパニックすることを確認するテストが書けるが、panicの原因が何であれテストを通過してしまう。そのため、panicの原因を特定するためにexpectを使うとより厳密なテストができる。
#[test]
#[should_panic(expected = "Rectangle width and height must be positive")]
fn negative_width() {
let _rect = Rectangle::new(-10, 10);
}
18.3 Iterators.collect()
collectメソッドでイテレータをコレクション(ベクタ等)に変換できるが、Result型のイテレータをコレクションに変換することもできる。
問題
下記のresult_with_list関数を完成させる問題。
division_resultsはdivide関数を使ってnumbersの各要素を27で割った結果を格納するイテレータでdivide関数はResult型を返すため、division_resultsはResult型のイテレータとなる。
fn divide(a: i64, b: i64) -> Result<i64, DivisionError> {
if b == 0 {
return Err(DivisionError::DivideByZero);
}
if a == i64::MIN && b == -1 {
return Err(DivisionError::IntegerOverflow);
}
if a % b != 0 {
return Err(DivisionError::NotDivisible);
}
Ok(a / b)
}
// TODO: Add the correct return type and complete the function body.
// Desired output: `Ok([1, 11, 1426, 3])`
fn result_with_list() -> Result<Vec<i64>, DivisionError> {
let numbers = [27, 297, 38502, 81];
let division_results = numbers.into_iter().map(|n| divide(n, 27));
}
解説
collectメソッドを使わずにResult型のイテレータをコレクションに変換する場合は下記のように書く。
fn result_with_list() -> Result<Vec<i64>, DivisionError> {
let numbers = [27, 297, 38502, 81];
let division_results = numbers.into_iter().map(|n| divide(n, 27));
let mut results = Vec::new();
for result in division_results {
results.push(result?);
}
Ok(results)
}
上記の実装部分を全てcollectメソッドで書くこともできる。
collectメソッドはResult型のイテレータをコレクションに変換する際に、Result型の中身がOkであればその値を格納し続け、Errが出た場合はその時点でcollectメソッドを終了し、Errを返す。
さらに、関数の返り値から型を推論できるため、division_results.collect()とするだけで良い。
fn result_with_list() -> Result<Vec<i64>, DivisionError> {
let numbers = [27, 297, 38502, 81];
let division_results = numbers.into_iter().map(|n| divide(n, 27)).collect();
division_results.collect()
}
21.3 Export Macros
モジュール内のマクロを他のモジュールから使う方法。
問題
macrosモジュール内のmy_macroマクロをmain関数内で使うにはどうすれば良いか?
// TODO: Fix the compiler error without taking the macro definition out of this
// module.
mod macros {
macro_rules! my_macro {
() => {
println!("Check out my macro!");
};
}
}
fn main() {
my_macro!();
}
解説
2通りの方法があり、公式の解法は古い書き方になっている。
古い書き方
macro_useアトリビュートを使う方法。
#[macro_use]
mod macros {
macro_rules! my_macro {
() => {
println!("Check out my macro!");
};
}
}
fn main() {
my_macro!();
}
新しい書き方
useを使う方法。この方法だとマクロの定義がされるより上の行でもマクロが使える。
mod macros {
macro_rules! my_macro {
() => {
println!("Check out my macro!");
};
}
pub(crate) use my_macro;
}
fn main() {
macros::my_macro!();
}