char::from_u32(@Rust)

Beginning Rustの「基本データ」の第6章に0~255までのu8形式の整数を文字表示できる整数の範囲から表示するのに、以下のようなコードが出てきますが、

fn main() {
    for n in 32..127 {
        println!("{}: [{}]", n, n as u8 as char);
    }
    for n in 160..256 {
        println!("{}: [{}]", n, n as u8 as char);
    }
}

ここで、

n as u8 as char

の代わりに、

どの数(u8以外)でも一文字に変換したいときには、char::from_u32()を使えとあって、それを使うと以下のようなコードになります。

fn main() {
    for n in 32..127 {
        println!("{}: [{}]", n, char::from_u32(n).unwrap());
    }
    for n in 160..256 {
        println!("{}: [{}]", n, n as u8 as char);
    }
}

char::from_u32()関数の解説は、

https://doc.rust-lang.org/core/primitive.char.html#method.from_u32

にあるように戻りの形はSome()なので、中身を取り出すためにunwrap()を使っています。オリジナルよりもこの方が見やすいように思います、処理速度は遅くなりそうですが。

 

admin

懇切丁寧

Rustの標準のネット上のテキストもほぼ通しで読んでみたので、まとめとして本のテキストを買ってみた。

インプレス本なので、懇切丁寧と言える記述内容ですが、Rustの全領域をカバーしているわけではなくて基本的な部分をサンプルコードを使って完全習得というのを目的にしているようです。この一冊をクリアすれば、中級レベルの素材もそれほど無理なくこなせるというような内容でしょうか。

 

admin

 

 

 

フルパス指定(Fully qualified path)(@Rust)

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)

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 prefix(@Rust)

これはコンパイル時にサイズが決定できないので&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

Visual Scripting(Unity)

Visual Scriptingは最近のゲームエンジンではかなり標準のようで、Unityのtutorialにも紹介されています。

https://learn.unity.com/project/roll-a-ball-visual-scripting-jp?uv=2021.2

Mx MacでもUnityはサポートされているので、動かしてみましたが、ゲーム版のノーコード作成ツールですねこれは。

<環境>

・M1 Mac Air : メモリ16GB

おそらくメモリ8GBだと辛いと思う、16GBでもメモリは12GB程度消費して、CPUリソースは30%ぐらい消費してパームレストは熱くなっていたから。ちなみにintel MacのVMware上のUbuntuではメモリ不足(8GB割り当て)のせいか途中でノーレンスポンス状態、

・Unity version : 2022.3.32f1

tutorialに物理条件(Physic Material)追加して、落下して粘土のようにプレートに付着するのでボールのように自然に跳ね返りするようにしましたがこれもノーコードで実現可能。

 

<Visual Scriptingのサンプル>

Itemの消滅対応のスクリプトですが、On Trigger Enterでcollisionを検出して、collision先がPlayerであればIfブロックでTrue出力してThis(自分自身)をDestroyするという流れになっているのが分かります。この程度だと分かりやすいけれども、ロジックが複雑化すると画面に納まりきらなそうなのは、たとえばScratchのようなブロック言語でも同じ。

Visual Scriptingで検索すると、Unityに限らず他のゲームエンジンでも実装されているようなので、ゲームは開発を役割分担しているはずなのでプログラマー以外のたとえばデザイナーにはこのようなツールは必需品とも言えるのではないかと思います。

 

admin

CopyとClone(@Rust)

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, Box)にはCloneだけderiveコードを見るとCopyは暗黙(implicit)であり、Cloneはmoved_pair.clone()としているから明示的(explicit)であることがわかります。

 

admin

impl traitはstaticである(@Rust)

クロージャーは動的(コンパイル時点でサイズが決まっていない)なので、コンパイル可能とするためには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

 

 

iter()のfilter()内のクロージャー表記について(@Rust)

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 との比較が行われていないことを確認するためです。

まとめ:

  • && は、イテレータから渡される要素への 2 重参照 を表し、クロージャ内で要素にアクセスできるようにします。
  • && は 短絡評価 されるため、パフォーマンス面での影響はありません。
  • 冗長に見えますが、意図せぬ 0 との比較を防ぎ、コードの可読性を向上させる役割を果たします。

補足

  • クロージャ内の変数 x は、イテレータから渡される各要素への 不変参照 です。 つまり、クロージャ内で x を変更することはできません。
  • コードをより簡潔に記述したい場合は、filter(|&x| x > 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