[Rust] 入門覚書
まずはドキュメント読みながら、書き方を覚えていく
日本語版ドキュメント
スライスって何に使うんだろう
文字列の一部を参照したくなる可能性があるのと同様、配列の一部を参照したくなる可能性もあります。 以下のようにすれば、参照することができます:
fn main() {
let a = [1, 2, 3, 4, 5];
let slice = &a[1..3];
}
部分的に参照したい時に使う感じか?
とりあえず存在は覚えておくけど、実際に出てくるまではワカラン
フィールドのないユニット様よう構造体
また、一切フィールドのない構造体を定義することもできます!これらは、()、ユニット型と似たような振る舞いをすることから、 ユニット様構造体と呼ばれます。ユニット様構造体は、ある型にトレイトを実装するけれども、 型自体に保持させるデータは一切ない場面に有効になります。トレイトについては第10章で議論します。
struct Unit {}
fn main() {
let unit = Unit {};
}
トレイトが分かるまで、謎
10章まで待てないので、チラ見
トレイトは、Rustコンパイラに、特定の型に存在し、他の型と共有できる機能について知らせます。 トレイトを使用すると、共通の振る舞いを抽象的に定義できます。トレイト境界を使用すると、 あるジェネリックが、特定の振る舞いをもつあらゆる型になり得ることを指定できます。
trait GameObject {
fn render(&self) -> bool;
}
struct Human {
hp: u8,
mp: u8,
position: (u32, u32),
}
struct Wall {
position: (u32, u32),
}
struct UnitItem {}
impl GameObject for Human {
fn render(&self) -> bool {
if self.hp > 10 && self.mp > 10 {
println!("Human Position: {}, {}", self.position.0, self.position.1);
}
return true;
}
}
impl GameObject for Wall {
fn render(&self) -> bool {
println!("Wall Position: {}, {}", self.position.0, self.position.1);
return true;
}
}
impl GameObject for UnitItem {
fn render(&self) -> bool {
println!("HogeHoge");
return true;
}
}
fn main() {
let wall = Wall { position: (0, 10) };
wall.render();
let human = Human {
hp: 100,
mp: 100,
position: (10, 20),
};
human.render();
let unit = UnitItem {};
unit.render();
}
Finished dev [unoptimized + debuginfo] target(s) in 0.01s
Running `target\debug\sandbox.exe`
Wall Position: 0, 10
Human Position: 10, 20
HogeHoge
フィールドは要らんけど、traitで書いといた関数だけ実装させたい みたいな感じ?
使い道はまだ分からんけど・・・
printlnで出すにはuse std::fmt::Debug
を実装するか #[derive(Debug)]
をつける
#[derive(Debug)]
struct Rectangle {
width: u32,
height: u32,
}
fn main() {
let rect1 = Rectangle {
width: 30,
height: 50,
};
println!("rect1 is {:#?}", rect1);
}
Finished dev [unoptimized + debuginfo] target(s) in 0.02s
Running `target\debug\sandbox.exe`
rect1 is Rectangle {
width: 30,
height: 50,
}
OR
use std::fmt::Debug;
struct Rect {
h: u32,
w: u32,
}
impl Debug for Rect {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("[Rect]")
.field("height", &self.h)
.field("width", &self.w)
.finish()
}
}
fn main() {
let rect = Rect { h: 1920, w: 1080 };
println!("Rect -> {:?}", rect);
}
Finished dev [unoptimized + debuginfo] target(s) in 0.28s
Running `target\debug\sandbox.exe`
Rect -> [Rect] { height: 1920, width: 1080 }
#[derive(Debug)]
struct Rectangle {
width: u8,
height: u8,
}
impl Rectangle {
fn area(&self) -> u32 {
return u32::from(self.width) * u32::from(self.height);
}
}
fn main() {
let rect1 = Rectangle {
width: 255,
height: 255,
};
println!("{}", rect1.area());
}
定義はu8で、計算結果がu8超える場合は、計算時にキャストするのかな?
定義をu32にするのは勿体ないとかあるのだろうか。
VSCodeで型書いてないときに、自動で表示されるの便利なんだけど、横に長くなるので以下を設定。
"editor.inlayHints.enabled": "offUnlessPressed"
Ctrl + Altで表示できるのアツイ
文字列をピッグ・ラテン(訳注: 英語の言葉遊びの一つ)に変換してください。各単語の最初の子音は、 単語の終端に移り、"ay"が足されます。従って、"first"は"irst-fay"になります。ただし、 母音で始まる単語には、お尻に"hay"が付け足されます("apple"は"apple-hay"になります)。 UTF-8エンコードに関する詳細を心に留めておいてください!
fn main() {
assert_eq!("english-hay", task("english"));
assert_eq!("irst-fay", task("first"));
assert_eq!("apple-hay", task("apple"));
}
fn task(input: &str) -> String {
let mut output = String::new();
match &input[..1] {
"a" | "i" | "u" | "e" | "o" => {
output.push_str(&input);
output.push_str("-hay");
}
_ => {
output.push_str(&input[1..]);
output.push_str("-");
output.push_str(&input[..1]);
output.push_str("ay");
}
}
return output;
}
そのまんま書いたけど、絶対もっとRustっぽくかけるはず…
デフォルト実装を、そのメソッドをオーバーライドしている実装から呼び出すことはできないことに注意してください。
super.hogehoge()的なのは無いのね。
糖衣構文
- トレイト境界(trait bound)
- impl trait
pub fn notify<T: Summary>(item: &T) {
println!("Breaking news! {}", item.summarize());
}
pub fn notify(item: &impl Summary) {
println!("Breaking news! {}", item.summarize());
}
ただし、impl Traitは一種類の型を返す場合にのみ使えます。 たとえば、以下のように、戻り値の型はimpl Summaryで指定しつつ、NewsArticleかTweetを返すようなコードは失敗します:
fn returns_summarizable(switch: bool) -> impl Summary {
if switch {
NewsArticle {
headline: String::from(
"Penguins win the Stanley Cup Championship!",
),
location: String::from("Pittsburgh, PA, USA"),
author: String::from("Iceburgh"),
content: String::from(
"The Pittsburgh Penguins once again are the best \
hockey team in the NHL.",
),
}
} else {
Tweet {
username: String::from("horse_ebooks"),
content: String::from(
"of course, as you probably already know, people",
),
reply: false,
retweet: false,
}
}
}
じゃあどうするのか?
多分こう
fn returns_summarizable(switch: bool) -> Box<dyn Summary> {
if switch {
Box::new(NewsArticle {
headline: String::from("Penguins win the Stanley Cup Championship!"),
location: String::from("Pittsburgh, PA, USA"),
author: String::from("Iceburgh"),
content: String::from(
"The Pittsburgh Penguins once again are the best \
hockey team in the NHL.",
),
})
} else {
Box::new(Tweet {
username: String::from("horse_ebooks"),
content: String::from("of course, as you probably already know, people"),
reply: false,
retweet: false,
})
}
}
fn largest<T: PartialOrd + Copy>(list: &[T]) -> T {
let mut largest = list[0];
for &item in list {
if item > largest {
largest = item;
}
}
largest
}
largestの別の実装方法は、関数がスライスのT値への参照を返すようにすることです。 戻り値の型をTではなく&Tに変え、それにより関数の本体を参照を返すように変更したら、 CloneやCopyトレイト境界は必要なくなり、ヒープ確保も避けられるでしょう。 これらの代替策をご自身で実装してみましょう!
fn largest<T: PartialOrd>(list: &[T]) -> &T {
let mut largest = &list[0];
for item in list {
if item > largest {
largest = item;
}
}
largest
}
ライフタイム注釈
fn first_word<'a>(s: &'a str) -> &'a str {
3つの規則を試して、ライフタイムを計算できる場合、省略可能
- 参照である各引数は、独自のライフタイム引数を得る
- 1つだけ入力ライフタイム引数があるなら、そのライフタイムが全ての出力ライフタイム引数に代入される
- 複数の入力ライフタイム引数があるけれども、メソッドなのでそのうちの一つが&selfや&mut selfだったら、 selfのライフタイムが全出力ライフタイム引数に代入される
テストでは成功時のprintln!は表示されない
→ cargo test -- --nocapture
テストを並列で実行しない
→ cargo test -- --test-threads=1
単体テスト
各モジュール内で実施
pub fn add10(x: u32) -> u32 {
x + 10
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn test_add10() {
assert_eq!(15, add10(5))
}
}
結合テスト
srcと同階層にtestsディレクトリを配置
extern crate sandbox;
#[test]
fn test_add10() {
assert_eq!(20, sandbox::add10(10));
}
長くなっているので、12章以降は別途。