ChatGPTにRustの勉強を手伝ってもらう - Enum編 -
これは何
普段Rustの勉強はThe Rust Programming Languageを読み進める形ですすめています。
しかしただ読むだけだと片っ端から以前の内容を忘れていき身になった感じがしないため、ChatGPTに練習問題を作成してもらうことにしました。
ここではChatGPTがどんな問題を作ってくれて、それに対してどう回答したかみたいな内容を記録していこうと思っています。
Enum編
GW挟んだせいでEnumについて全て忘れました。もう一回読み直します
読み直した上で練習問題
ChatGPTに練習問題作ってくれないか相談
I'm trying to get myself familiar with enum in Rust.
Can you give me some exercises??
快諾の上以下お題を作成してくれました
Exercise 1:
Create an enum called Direction that represents the four cardinal directions: North, South, East, and West. Implement a function called opposite_direction that takes a Direction as input and returns the opposite direction. For example, if the input is Direction::North, the function should return Direction::South.
Exercise 2:
Create an enum called Shape that represents different shapes: Circle, Square, and Triangle. Implement a function called calculate_area that takes a Shape as input and returns the area of the shape. Assume that the shapes are centered at the origin and have a unit radius/side length. You can use simple formulas to calculate the areas (e.g., πr^2 for a circle).
Exercise 3:
Create an enum called Result<T, E> that represents the result of an operation. It should have two variants: Ok(T) to indicate a successful result with a value of type T, and Err(E) to indicate an error with a value of type E. Implement a function called divide that takes two integers as input and returns a Result indicating the result of the division. If the second integer is zero, return an Err variant with an appropriate error message; otherwise, return an Ok variant with the result of the division.
Exercise 1の回答
Direction
というenumを作成し、方角をvariantとするという問題。
加えてDirection
に付随するopposite
というfunctionを定義、呼ばれた際にはparameterとして受け取ったDirection
とは反対の方角を返すように実装する、とのこと。
とりあえず以下のように実装してみました。
fn main() {
#[derive(Debug)]
enum Direction {
North,
East,
South,
West
}
impl Direction {
fn opposite(&self) -> Direction {
match self {
Direction::North => Direction::South,
Direction::East => Direction::West,
Direction::South => Direction::North,
Direction::West => Direction::East,
}
}
}
let north = Direction::North;
println!("{:?}", north.opposite());
}
ChatGPTの反応。褒めてくれる、うれしい。
Your solution for Exercise 1 looks great! You've defined the Direction enum with its four variants: North, East, South, and West. The opposite method is implemented for the Direction enum, allowing you to find the opposite direction based on the current direction.
しかしここで、「あれこれassociated functionとして定義したけど、別にその必要なくてむしろ普通のfunctionとして作成した方がよかったのでは、、?」という疑問が。
ChatGPTに聞いてみると、
I apologize for any confusion caused by the way the question was presented. You are correct that the wording of the exercise could have been clearer. In the exercise, you were asked to implement a function called opposite_direction that takes a Direction as input and returns the opposite direction.
While implementing opposite_direction as an associated function of the Direction enum is one way to approach the exercise, it is not the only valid approach. Defining opposite_direction as a standalone function would be perfectly acceptable as well.
Here's an example of implementing opposite_direction as a standalone function:
#[derive(Debug)]
enum Direction {
North,
South,
East,
West,
}
fn opposite_direction(dir: Direction) -> Direction {
match dir {
Direction::North => Direction::South,
Direction::South => Direction::North,
Direction::East => Direction::West,
Direction::West => Direction::East,
}
}
fn main() {
let current_direction = Direction::North;
let opposite = opposite_direction(current_direction);
println!("Opposite direction: {:?}", opposite);
println!("Current direction: {:?}", current_direction);
}
ChatGPT的にはどっちでもよかったらしく、methodではない実装例をあげてくれた。
Exercise 2の回答
Shape
というenumを作成し、variantにはCircle
・Square
・Triangle
を。
加えてShape
に付随するcalculate_area
というfunctionを定義し、parameterとして受け取ったShape
の面積を求めるというもの。
今回は面積を求めるということなので、Selfを最初のパラメータとして受け取ることができるassociated functionで定義しちゃって良さそうだなと考え以下のように実装
fn main() {
#[derive(Debug)]
enum Shape {
Circle {
radius: f64
},
Square {
width: f64,
height: f64
},
Triangle {
base: f64,
height: f64
}
}
impl Shape {
fn calculate_area(&self) -> f64 {
match self {
Shape::Circle {radius} => radius * 2.0 * 3.14,
Shape::Square {width, height} => width * height,
Shape::Triangle {base, height} => (base * height) / 2.0,
}
}
}
let circle = Shape::Circle {radius: 3.0};
println!("Circle: {:?}", circle.calculate_area());
let square = Shape::Square {width: 2.0, height: 2.0};
println!("Square: {:?}", square.calculate_area());
let triangle = Shape::Triangle {base: 5.0, height: 20.0};
println!("Triangle: {:?}", triangle.calculate_area());
}
またしても褒めてくれる。
Your solution for Exercise 2 is well done! You've defined the Shape enum with three variants: Circle, Square, and Triangle. Each variant contains the necessary fields to represent the corresponding shape.
しかしながらより良い解があるのではと、実装例を見せて欲しいとお願いすると
enum Shape {
Circle(f64),
Square(f64),
Triangle(f64, f64),
}
impl Shape {
fn calculate_area(&self) -> f64 {
match self {
Shape::Circle(radius) => std::f64::consts::PI * radius.powi(2),
Shape::Square(side_length) => side_length.powi(2),
Shape::Triangle(base, height) => 0.5 * base * height,
}
}
}
fn main() {
let circle = Shape::Circle(3.0);
println!("Circle: {:?}", circle.calculate_area());
let square = Shape::Square(2.0);
println!("Square: {:?}", square.calculate_area());
let triangle = Shape::Triangle(5.0, 20.0);
println!("Triangle: {:?}", triangle.calculate_area());
}
確かにChatGPTの回答通りtuple like・struct likeどちらで値を持たせるかはおいておいて、少なくともCircle
・Square
については持たせる値は一つで良かったはず。
加えてstd::f64::consts::PI
とかside_length.powi(2)
といった便利な書き方を教えてくれるのありがたい。
Exercise 3の回答
Result<T, E>
というenumを作成し、variantにはOkay(T)
・Err(E)
を。
次にdevide
というfunctionを作成。パラメータで2つのintegerを受け取り、以下の処理を実施する。
- 2つ目のパラメータが
0
の場合Err
でラップしたエラーメッセージを返す。 - それ以外の場合は割り算の結果を
Okay
でラップして返す
以下実装。devide
の中match
使用せずにif
で十分なはずと思いながらも使ってみたかったのでmatch
使用して実装をしてみる。
fn main() {
#[derive(Debug)]
enum Result<T, E> {
Okay(T),
Err(E)
}
fn divide(num1: i32, num2: i32) -> Result<i32, String> {
match num2 {
0 => Result::Err(String::from("Can't take 0, sorry m8")),
_ => Result::Okay(num1 / num2)
}
}
let calc1 = divide(9, 3);
println!("Calc 1: {:?}", calc1);
let calc2 = divide(5, 0);
println!("Calc 2: {:?}", calc2);
}
やっぱり褒めてくれる。
Your solution for Exercise 3 is well done! You've defined the Result<T, E> enum, which is similar to the built-in Result enum in Rust. It has two variants: Okay(T) to represent a successful result with a value of type T, and Err(E) to represent an error with a value of type E.
ChatGPTにも実装をするようお願いすると、
enum Result<T, E> {
Ok(T),
Err(E),
}
fn divide(num1: i32, num2: i32) -> Result<i32, String> {
if num2 == 0 {
Result::Err(String::from("Can't divide by zero"))
} else {
Result::Ok(num1 / num2)
}
}
fn main() {
let calc1 = divide(9, 3);
match calc1 {
Result::Ok(result) => println!("Calc 1: {:?}", result),
Result::Err(err) => println!("Calc 1 error: {:?}", err),
}
let calc2 = divide(5, 0);
match calc2 {
Result::Ok(result) => println!("Calc 2: {:?}", result),
Result::Err(err) => println!("Calc 2 error: {:?}", err),
}
}
devide
の中ではif
を使ってその後Result
から帰ってきた値を抜き出すところでmatch
を使ってる。
最初の自分の回答ではResult
の中から値を抜き出すことはせずそのままprintしているため、ChatGPTの方がより丁寧な印象。
match
の使い所もこっちの方が正しい気がする
以上ChatGPTにRustの勉強を手伝ってもらう - Enum編 - でした!
聞き方や扱うトピックによって難易度に若干のばらつきはあるものの、読んだチャプターの振り返・書き方しっかり覚えたい時などなど、これからも重宝しそうな使い方だなぁという印象でした。
Discussion