Rustの標準のネット上のテキストもほぼ通しで読んでみたので、まとめとして本のテキストを買ってみた。
インプレス本なので、懇切丁寧と言える記述内容ですが、Rustの全領域をカバーしているわけではなくて基本的な部分をサンプルコードを使って完全習得というのを目的にしているようです。この一冊をクリアすれば、中級レベルの素材もそれほど無理なくこなせるというような内容でしょうか。
admin
la vie libre
https://doc.rust-jp.rs/rust-by-example-ja/trait/disambiguating.html
に出てくるコードで最後の4行は
<Type as Trait>::function(receiver_if_method, next_arg, …);
のようなフルパス指定ししなくとも
let username = UsernameWidget::get(&form);
assert_eq!("rustacean".to_owned(), username);
let age = AgeWidget::get(&form);
assert_eq!(28, age);
と書いて、Traitを指定するだけでもコンパイルできます。関数の引数に(&form)、つまりFormのインスタンスを使っているからだと思いますが。
一方関連関数new()を追加した以下のコードだと、
trait UsernameWidget {
// Get the selected username out of this widget
fn get(&self) -> String;
fn new(name: String, age: u8) -> Self;
}
trait AgeWidget {
// Get the selected age out of this widget
fn get(&self) -> u8;
}
// A form with both a UsernameWidget and an AgeWidget
struct Form {
username: String,
age: u8,
}
impl UsernameWidget for Form {
fn get(&self) -> String {
self.username.clone()
}
fn new(str: String, age: u8) -> Self {
Form {
username: str,
age: age,
}
}
}
impl AgeWidget for Form {
fn get(&self) -> u8 {
self.age
}
}
fn main() {
// both UsernameWidget & AgeWidget are implemeneted to Form, you can use Form::new() instead of
// the fully-qualified path <form as="" usernamewidget="">::new(...)
let form = Form::new(String::from("John"), 30);
let username = UsernameWidget::get(&form);
assert_eq!("John".to_owned(), username);
let age = AgeWidget::get(&form); assert_eq!(30, age);
}
もし、
let form = Form::new(String::from("John"), 30);
を
let form = UsernameWidget::new(String::from("John"), 30);
とするとこれはコンパイルできない(use the fully-qualified path to the only available implementationのようなエラーが出る)、なぜなら関連関数(Associated function)だから構造体情報を持たないので対象の構造体を特定できないから。
しかしソースコードの最後のほうの行で、オリジナルソースのようにフルパスを指定してやれば、
let form = UsernameWidget::new(String::from("John"), 30);
でもコンパイルできる。これはコンパイラが構造体FormとトレイトUserWidgetの関連を他の行からの情報で知ることができるようになったからだろう。
構造体Formにアクセスするだけなら、Form::new(…..)を使うのが最もコンパクトで分かりやすいコードになりますが、これはimpl Trait for Structがあるから有効なコード。
フルパス記法については、
https://doc.rust-jp.rs/book-ja/ch19-03-advanced-traits.html
がオリジナルのテキストになります。
admin
Rustに継承はないですが、似たような機能ということであるトレイトの上位トレイトを定義可能です。
Trait_b: Trait_aのような記法でTrait_aがスーパートレイトでTarit_bはサブトレイトになります。
https://doc.rust-jp.rs/rust-by-example-ja/trait/supertraits.html
にサンプルコードがありますが、main()から呼べるようにしたのが以下のコードでGeminiで追加部分のコードをジェネレートしましたが、構造体CSStudentを定義して必要な関数を実装し、main()からは構造体のインスタンスを作成して関数comp_sci_student_greeting()
を呼び出しています。
// Define a struct that implements CompSciStudent
struct CSStudent {
name: String,
university: String,
fav_lang: String,
git_username: String,
}
trait Person {
fn name(&self) -> String;
}
// Person is a supertrait of Student.
// Implementing Student requires you to also impl Person.
trait Student: Person {
fn university(&self) -> String;
}
trait Programmer {
fn fav_language(&self) -> String;
}
// CompSciStudent (computer science student) is a subtrait of both Programmer
// and Student. Implementing CompSciStudent requires you to impl both supertraits.
trait CompSciStudent: Programmer + Student {
fn git_username(&self) -> String;
}
fn comp_sci_student_greeting(student: &dyn CompSciStudent) -> String {
format!(
"My name is {} and I attend {}. My favorite language is {}. My Git username is {}",
student.name(),
student.university(),
student.fav_language(),
student.git_username()
)
}
impl Person for CSStudent {
fn name(&self) -> String {
self.name.clone()
}
}
impl Student for CSStudent {
fn university(&self) -> String {
self.university.clone()
}
}
impl Programmer for CSStudent {
fn fav_language(&self) -> String {
self.fav_lang.clone()
}
}
impl CompSciStudent for CSStudent {
fn git_username(&self) -> String {
self.git_username.clone()
}
}
fn main() {
let bob = CSStudent {
name: "Bob".to_string(),
university: "Cambridge".to_string(),
fav_lang: "Ruby".to_string(),
git_username: "claw".to_string(),
};
println!("{}", comp_sci_student_greeting(&bob));
}
ここで&dyn CompSciStudentの記法がありますが、トレイトオブジェクトと呼ばれる動的ディスパッチです。
これはコンパイル時にサイズが決定できないので&dynを使い、またdynはfat pointerです。さらに&dyn CompScistudentはimpl CompScistudentでもコンパイル可能で、impl Traitで静的に呼び出しもできます。静的指定に比べると多少なりとも負荷は増加しますが、
https://isehara-3lv.sakura.ne.jp/blog/2024/06/04/impl-traitはstaticであるrust/
admin
Rustの場合のCopyとCloneの例を見ると、
https://doc.rust-lang.org/std/marker/trait.Copy.html
のコードからlet y = xのように代入すると、
<これはmove>
#[derive(Debug)]
struct Foo;
let x = Foo;
let y = x;
// `x` has moved into `y`, and so cannot be used
// println!("{x:?}"); // error: use of moved value
したがって元のxにはアクセスできない。
<CopyをderiveすればCopy、ただしCloneもderiveしないとダメ>
“We can derive a `Copy` implementation. `Clone` is also required, as it’s
a super trait of `Copy`.”
// We can derive a `Copy` implementation. `Clone` is also required, as it's
// a supertrait of `Copy`.
#[derive(Debug, Copy, Clone)]
struct Foo;
let x = Foo;
let y = x;
// `y` is a copy of `x`
println!("{x:?}"); // A-OK!
Cloneのドキュメントへのリンクは、
https://doc.rust-lang.org/std/clone/trait.Clone.html
さて、
https://doc.rust-jp.rs/rust-by-example-ja/trait/clone.html
のコードを再掲すると、
// A unit struct without resources
// Clone must also have to be derived when you use Copy
#[derive(Debug, Clone, Copy)]
struct Unit;
// A tuple struct with resources that implements the `Clone` trait
// Copy can not be applied to Pair struct
#[derive(Clone, Debug)]
struct Pair(Box, Box);
fn main() {
// Instantiate `Unit`
let unit = Unit;
// Copy `Unit`, there are no resources to move
let copied_unit = unit;
// Both `Unit`s can be used independently
println!("original: {:?}", unit);
println!("copy: {:?}", copied_unit);
// ----------- from here, Pair is handled -----------
// Instantiate `Pair`
let pair = Pair(Box::new(1), Box::new(2));
println!("original: {:?}", pair);
// Move `pair` into `moved_pair`, moves resources
let moved_pair = pair;
println!("moved: {:?}", moved_pair);
// Error! `pair` has lost its resources
//println!("original: {:?}", pair);
// TODO ^ Try uncommenting this line
// Clone `moved_pair` into `cloned_pair` (resources are included)
let cloned_pair = moved_pair.clone();
println!("cloned original: {:?}", moved_pair);
// Drop the original pair using std::mem::drop
drop(moved_pair);
// Error! `moved_pair` has been dropped
//println!("copy: {:?}", moved_pair);
// TODO ^ Try uncommenting this line
// The result from .clone() can still be used!
println!("clone: {:?}", cloned_pair);
}
構造体の要素がプリミティブな型だとCopyが適用できるけれども、Box<i32>のような要素を持つとCopyは適用できない。
このソースを見ると、struct UnitにはCopyとCloneがderivesされていますがstruct Pair(Box
コードを見るとCopyは暗黙(implicit)であり、Cloneは
明示的(explicit)であることがわかります。
admin
クロージャーは動的(コンパイル時点でサイズが決まっていない)なので、コンパイル可能とするためにはBox<dyn fn() >の様な形でヒープエリアに割り当てることが必要ですが、imple traitにするだけで静的(static)として扱える様になると。以下のreturns_closureとreturns_closure0関数は同じことをやっていますが、impl Trait(ここではFnトレイト)を使うと静的になるので見かけはスッキリしています。
// imple "Trait" is static, so you don't need to use "Box dyn"
fn main() {
fn returns_closure(y: i32) -> impl Fn(i32) -> i32 {
move |x| x + y
}
fn returns_closure0(y: i32) -> Box<dyn fn(i32) -> i32> {
Box::new(move |x| x + y)
}
println!("{}", returns_closure0(50)(10));
println!("{}", returns_closure(50)(20));
}
admin
fn double_positives<'a>(numbers: &'a Vec) -> impl Iterator- + 'a {
numbers
.iter()
.filter(|x| x > &&0)
.map(|x| x * 2)
}
fn main() {
let singles = vec![-3, -2, 2, 3];
let doubles = double_positives(&singles);
assert_eq!(doubles.collect::<Vec<i32>>(), vec![4, 6]);
}
https://doc.rust-jp.rs/rust-by-example-ja/trait/impl_trait.html
なぜここで二重参照記号なのか?
.filter(|x| x > &&0)
Geminiに聞くと、
———————————
iter().filter(|x| x > &&0) における && の必要性
上記のコードは、Rust でイテレータを用いて要素をフィルタリングする例です。 && は論理積演算子であり、このコードにおいて 2 つの重要な役割を果たします。
1. 参照の借用:
Rust では、所有権と借用の概念に基づいてメモリ管理が行われます。 iter() メソッドは、コレクションの要素への 参照 を返すイテレータを生成します。 この参照は、& 演算子によって示されます。
一方、クロージャ |x| x > &&0 は、イテレータから渡される各要素に対して評価されます。 このクロージャ内でも要素への参照にアクセスする必要があるため、& 演算子を用いて 2 重参照 &&x とする必要があります。
2. 短絡評価:
&& 演算子は 短絡評価 される性質を持ちます。 これは、左側のオペランドが false である場合、右側のオペランドが評価されずに false を返すことを意味します。
この例では、x > 0 は常に true を返します。 つまり、たとえ &&0 が存在しなくても、結果は常に true となります。 しかし、&& を省略すると、コンパイラから警告が出力されます。 これは、意図せずに 0 との比較が行われていないことを確認するためです。
まとめ:
補足
———————————
上記で、filter(|&x| x > 0)は嘘(&&は形式的には論理積演算子だけど実は二重参照だからこの説明も微妙な説明)で、以下のようにしないとコンパイルは通りません。生成A.I.はそれらしい回答は出してきますが、正しいかどうかは別なので、解決の手掛かりをうるために使うのが現段階では正解だろうと思う。
.filter(|&x| x > &0)
あるいは、
.filter(|x| *x > &0)
あるいは、
.filter(|x| **x > 0)
あるいは、
.filter(|&x| *x > 0)
もしくは、
.filter(|&&x| x > 0)
ということで比較対象とするには参照記号や参照外しで、比較の『次元』を合わせないといけないということ。
https://doc.rust-lang.org/std/iter/trait.Iterator.html#method.filter
をみると、以下のような記述(クロージャーはfilter()に参照渡しだから二重参照になると)になってます
Because the closure passed to filter() takes a reference, and many iterators iterate over references, this leads to a possibly confusing situation, where the type of the closure is a double reference:
クロージャーに関するこの辺りの説明は、
https://zenn.dev/mebiusbox/books/22d4c1ed9b0003/viewer/64c6f3
「静的と動的に関わる記述」で詳しいと思う。
admin
https://doc.rust-jp.rs/rust-by-example-ja/trait/dyn.html
におそらくdyn(dynamicの略)の典型的使い方(トレイトの戻り値型で必須のprefixキーワード)だろうと思われるコードが出てきます。
dynの説明、
https://doc.rust-lang.org/std/keyword.dyn.html
によると、dyn Traitは二つの参照ポインタを持つようで、
① データへの参照(この場合にはstruct)ポインタ情報
② 通称vtableと呼ばれる関数ポインタマップへのポインタ情報
struct Sheep {}
struct Cow {}
trait Animal {
// Instance method signature
fn noise(&self) -> &'static str;
}
// Implement the `Animal` trait for `Sheep`.
impl Animal for Sheep {
fn noise(&self) -> &'static str {
"baaaaah!"
}
}
// Implement the `Animal` trait for `Cow`.
impl Animal for Cow {
fn noise(&self) -> &'static str {
"moooooo!"
}
}
// Returns some struct that implements Animal, but we don't know which one at compile time.
fn random_animal(random_number: f64) -> Box<dyn Animal> {
if random_number < 0.5 {
Box::new(Sheep {})
} else {
Box::new(Cow {})
}
}
fn main() {
let random_number = 0.234;
let animal = random_animal(random_number);
println!("You've randomly chosen an animal, and it says {}", animal.noise());
}
戻り値には選択的に二種類の構造体のヒープエリアBox::new(struct{})を指定していますが、コンパイル時には”animal”はそれぞれの構造体にリンクされるので、戻り情報のBox::new(struct{})には”animal”関数も含まれている(上記の①と②)から”animal.noise()
“がアクセス可能となります。
もし戻り値の型が確定的であれば、例えばBox<Sheep>で問題ないのは蛇足で、確定できないからトレイトを戻り型にしている訳で、そのためのdynです。
動的なポインター割り当てなので、性能的には当然オーバーヘッドが出てきますが、コードの分かり易さの点からはこの記法で問題ないケースが大半だろうと思います。
admin
https://doc.rust-jp.rs/rust-by-example-ja/scope/lifetime/trait.html
のライフタイムアサーションに出てくるコード、
// A struct with annotation of lifetimes.
// ライフタイムのアノテーションつき構造体。
#[derive(Debug)]
struct Borrowed<'a> {
x: &'a i32,
}
// Annotate lifetimes to impl.
// ライフタイムのアノテーションつきimpl。
impl<'a> Default for Borrowed<'a> {
fn default() -> Self {
Self {
x: &10,
}
}
}
fn main() {
let b: Borrowed = Default::default();
println!("b is {:?}", b);
}
ここでなぜ#[derive(Default)]
ではダメなのか、それはまさしくBorrowed構造体のデータで参照を使っているので絶対にライフタイム指定が必要、かつライフタイムを指定した場合には構造体Borrowedに対してDefaultトレイトの標準実装では対応できないからカスタム実装が必要となります。
もしデータがmoveされる以下のコードならば、
// A struct with annotation of lifetimes.
// ライフタイムのアノテーションつき構造体。
#[derive(Debug, Default)]
struct Borrowed {
x: i32,
}
// Annotate lifetimes to impl.
// ライフタイムのアノテーションつきimpl。
/*
impl<'a> Default for Borrowed<'a> {
fn default() -> Self {
Self {
x: &10,
}
}
}
*/
fn main() {
let b: Borrowed = Borrowed{x: 5,};
println!("b is {:?}", b.x);
}
Defaultトレイトは標準実装で問題ありません
admin
ジェネリクスと関連し、ジェネリクスとの使い分けも必要ですが、関連型という記法があります。
https://zenn.dev/shinyay/articles/hello-rust-day040
のコードから、関連型で全体を構成してみました。
できるだけ固有の型記述を排除するために、impl Positionの
中ではi32指定とするのではなく&Self::X or Yを使っています。
fn main() {
// associated types
//
let point = Point(x, y);
println!("Point X:{}, Y:{}", &x, &y);
println!("Exist?:{}", point.exist(&x, &y));
println!("Point-X:{}", point.v_axis());
println!("Point-X:{}", point.h_axis());
new_point(&point);
}
struct Point(i32, i32);
trait Position {
type X; // to use associated types
type Y;
fn exist(&self, _: &Self::X, _: &Self::Y) -> bool;
fn h_axis(&self) -> i32;
fn v_axis(&self) -> i32;
}
impl Position for Point {
type X = i32;
type Y = i32;
fn exist(&self, x: &Self::X, y: &Self::Y) -> bool {
(&self.0 == x) && (&self.1 == y)
}
fn h_axis(&self) -> Self::X {
self.0
}
fn v_axis(&self) -> Self::Y {
self.1
}
}
fn new_point<Z: Position>(point: &Z) { // you don't need specify X and Y anymore
println!("POINT:({},{})", point.v_axis(), point.h_axis()) // like "fn new_point<X, Y, Z>(point: &Z) where Z: Position<X, Y>"
}
使い分けですが、この例のようなシンプルなトレイトとSelfが一対一対応(トレイトに実装される型が一種類の時)の場合には、関連型を使う方がコードが読みやすいだろうと思います。もしi32以外に例えばf64の実装もあるというような複数実装の時には素直にジェネリクスを使うしかありません。
admin
トレイトが何らの機能を持っていなくとも境界条件として使えるというサンプルコードです。
https://doc.rust-jp.rs/rust-by-example-ja/generics/bounds/testcase_empty.html
のコードにYellowトレイトを追加しています。
struct Cardinal;
struct BlueJay;
struct Turkey;
trait Red {}
trait Blue {}
trait Yellow {}
impl Red for Cardinal {}
impl Blue for BlueJay {}
impl Yellow for Turkey {}
// These functions are only valid for types which implement these
// traits. The fact that the traits are empty is irrelevant.
fn red<T: Red>(_: &T) -> &'static str { "red" }
fn blue<T: Blue>(_: &T) -> &'static str { "blue" }
fn yellow<T: Yellow>(_: &T) -> &'static str { "yellow" }
fn main() {
let cardinal = Cardinal;
let blue_jay = BlueJay;
let turkey = Turkey;
// `red()` won't work on a blue jay nor vice versa
// because of the bounds.
println!("A cardinal is {}", red(&cardinal));
println!("A blue jay is {}", blue(&blue_jay));
println!("A turkey is {}", yellow(&turkey));
}
このソースではライフタイム指定(‘static)はしなくとも動作します。
admin