TAKOYAKING’s blog 一覧

TAKOYAKING’s blog

たこ焼き系

Rust: 型推論で型(タイプヒント)の部分省略

イテレーターの特定メソッドについて調べていたら、型を部分省略する方法があったのでメモします。

イテレータ

Rust 公式チュートリアルより抜粋

もしあなたが覚えているなら、 ::<> 構文で型ヒント(type hint)を与え、整数型のベクタが欲しいと伝えることができます。かといって常に型をまるごとを書く必要はありません。 _ を用いることで部分的に推論してくれます。

let one_to_one_hundred = (1..101).collect::<Vec<i32>>();
let one_to_one_hundred = (1..101).collect::<Vec<_>>();

Rustでは型は一部分だけ記述する記法も用意されていて、

Vecと書かずに
Vec<_>と書いて
「i32(T)」の部分は「_」で省略できるみたいです。

一応左辺verも用意

let one_to_one_hundred: Vec<_> = (1..101).collect();

感想

忘れそうなのでメモ。

Rust: enumのコンストラクターは関数のようにふるまえる

stringの限界 | Rust by Example
Rust Examplesより

map_errの引数は関数であるのにenumを直接ドーンと代入している箇所があって、どういうことか疑問に思ったので調べてみました。

現象

map_errの引数はFnOnce(E) -> Fであるのに関数の代わりにenumを渡してもきちんと処理されていた。

use std::num::ParseIntError;
use std::fmt;

type Result<T> = std::result::Result<T, DoubleError>;

enum DoubleError {
    // このエラーに関しては、エラーの詳細を説明するのに追加の情報は必要ない。
    EmptyVec,
    // パースに失敗した場合はParseIntErrorの挙動に追従する。追加の情報を持たせたい
    // 場合は、この型に、より多くのデータを追加しなくてはならない。
    Parse(ParseIntError),
}

fn double_first(vec: Vec<&str>) -> Result<i32> {
    vec.first()
       // エラーを新しい型のものに変換
       .ok_or(DoubleError::EmptyVec)
       .and_then(|s| s.parse::<i32>()
            // ここでも新しいエラー型へとアップデートさせる。
            .map_err(DoubleError::Parse) // <=================ここ
            .map(|i| 2 * i))
}

検証

ドキュメントの型通りにmap_errを実装するなら

fn double_first(vec: Vec<&str>) -> Result<i32> {
    vec.first()
        .ok_or(DoubleError::EmptyVec)
        .and_then(
            |s| s.parse::<i32>()
            .map_err(|e|DoubleError::Parse(e))
            .map(|i| 2 * i)
        )
}

になると思ったし、これで問題なく動きます。
enumのタプル構造体を渡してもどちらでも動くのでもしかしてmap_errだけでなく、他でも動作するのではと思い、以下サンプルを試してみました。

#[derive(Debug)]
enum Osaka {
    Takoyaki(i32),
}

fn takoyaki<F>(f: F) where
    F: FnOnce(i32) -> Osaka
{
    println!("{:?}", f(7));
}

fn main() {
    takoyaki(Osaka::Takoyaki);
}

// 出力:Takoyaki(7)

できてしまいました・・・

検索すると出てきました
列挙型
以下抜粋

列挙型のコンストラクターは関数のようにも使うことができます

試しに意図的にエラーを起こして、エラーメッセージを読んでみます。

fn main() {
    println!("{}", Osaka::Takoyaki); // エラー
}

// fn(i32) -> Osaka {Osaka::Takoyaki}` cannot be formatted with the default formatter

すると型が判明し、fn(i32) -> Osakaだということがわかりました。

なんかこんなのもありました。
Rust 1.40.0でtuple構造体とenum variantのコンストラクタがconst fnになる - Qiita


感想

すっ飛ばそうと思ったけど、ちゃんと調べてよかった!スッキリ!

Rust: エラー処理のための「Try!」と「?」

try!入門 | Rust by Example
こちらのサンプルコードで

fn setup() -> std::io::Result<()> {
    let mut a = try!(File::create("a"));
    try!(a.write_all(b"grape"));

    let mut b = try!(File::create("b"));
    b.write_all(b"fruit")
}

を実行すると

expected expression, found reserved keyword `try`

が出てしまいました。

環境

rustc 1.40.0

原因

サンプルコードは古いRustなのですが、今現在、新しい記法になっているためです。

対処方法

1. try!の代わりに「?」をつける
fn setup() -> std::io::Result<()> {
    let mut a = File::create("a")?;
    a.write_all(b"grape")?;

    let mut b = File::create("b")?;
    b.write_all(b"fruit")
}

try!の代わりに「?」が導入されているので手軽にエラー処理を行うことができます。

2. try!の代わりに「r#try!」をつける
fn setup() -> std::io::Result<()> {
    let mut a = r#try!(File::create("a"));
    try!(a.write_all(b"grape"));

    let mut b = r#try!(File::create("b"));
    b.write_all(b"fruit")
}

r#をつけることでtry!マクロをギリギリまだ使用できるみたいです。

感想

「?」便利だけどなんと呼べばいいのかわからないので「はてな」と呼べば良いのでしょうか?

tryが予約語になっていた理由ですが、
rfcs/2388-try-expr.md at master · rust-lang/rfcs · GitHub
ここに書いてあって、色々議論された故の予約語だったようです。

Rust: map VS and_then

Rustのmapとand_thenが最初見たときにどちらも加工するという処理なのに、どういう時に使いわけるかイメージできなかったので、まとめてみました。
(Option型にも使用できますが、例はResultにしています。)

map

returnの型: Result
functionの型: FnOnce(T) -> U (Resultではない)
functionはErrなら呼ばれず、Okなら呼ばれる

and_then

returnの型: Result
functionの型: F: FnOnce(T) -> Result (Result!)
functionはErrなら呼ばれず、Okなら呼ばれる

and_thenとmapのサンプル

コンビネータを組み合わせる | Rust by Example

// ファイルからデータを取り出し、値を`Result`として返す。
fn get_data(path: &str) -> Result<String> {
    File::open(path)
        .map_err(|err| err.to_string())
        .and_then(|mut file| {
            let mut contents = String::new();

            // データを`contents`に読み込む。
            file.read_to_string(&mut contents)
                .map_err(|err| err.to_string())
                // `read_to_string`の返り値は無視し、`contents`を返す。
                .map(|_| contents)
        })
}

// 二つのファイルの中身を結合し、新しい`Result`にして返す。
fn concat(filename_a: &str, filename_b: &str) -> Result<String> {
    let (data_a, data_b) = (get_data(filename_a), get_data(filename_b));

    data_a.and_then(|a|
        // `a`と`b`が両方とも`Ok`ならば`Ok`を返し、そうでなければ
        // 先に`Err`を挙げた方の`Err`を返す。
        data_b.and_then(|b| Ok(a + &b))
    )
}

上の例を見るとand_thenはfnの中身がエラーを起こる可能性がある時に使われ、mapはエラーの可能性がない時に使われています。

https://users.rust-lang.org/t/what-is-the-difference-between-map-and-and-then/29108/2
ここにもand_thenは失敗する可能性のある一連の処理の時に使用すると書いてありました。

まとめ

mapはエラーがない時に使う
and_thenはエラーが発生する可能性がある時に使う

Enter Play Mode Optionsを設定した時にDoTweenがうまく動作しない時

先日、2019.3の機能のEnter Play Mode Optionsを設定しました。
Unity 2019.3 Enter Play Mode Optionsを設定すると爆速になった (条件あり) - TAKOYAKING’s blog
めちゃくちゃ速くなったのですが、使っていると、DoTweenが描画されないことに気づきました。

animationが2回目の時はなぜか描画されないことに気がつきました。
Enter Play Mode Optionsの設定をOffにすると正しく描画されるので、原因はEnter Play Mode Optionsを設定したことであることは明らかでした。

直し方

DoTweenをv1.2.335へアップデートする

注意点

アップデートする時に問題が発生したのですが、DoTweenが特定のバージョンよりも古いバージョンから更新する場合は更新手順にきっちり従う必要があるみたいです。

あとPreferencesの設定がリセットされているみたいですので、設定し直す必要があるかもしれません。

やり方

DOTween - Support
ここより抜粋(翻訳)

  • 新しいバージョンを古いバージョンと同じフォルダに上書きする (たくさんエラーが出るけど心配しないで!)
  • Unityを再起動
  • DOTween's Utility Panel を開いて「Setup DOTween...」を押して設定する

ここまでが公式の手順なのですが、これを行ってもうまくいきませんでした。
うまくいった方法は、上記に加えて

  • 「Create ASMDEF」を押して作るでした。

もしかしたらリセットされているかもしれないので

  • Preferencesの設定(任意)

「Create ASMDEF」

f:id:TAKOYAKING:20200301004315p:plain
作業は簡単で、このボタンを押して、OKをポンポンと押していくだけです。
これを行わないと、以下のようなDebugger何とかかんとかエラーが出続けることになります。

Assets/Demigiant/DOTween/Modules/DOTweenModuleUnityVersion.cs(90,30): error CS0117: 'Debugger' does not contain a definition for 'logPriority'

結果

これでEnter Play Mode Optionsを設定してもバッチリDoTweenしてくれました!

Unity 2019.3 Enter Play Mode Optionsを設定すると爆速になった (条件あり)

blogs.unity3d.com

こちらの記事でEnter Play Mode Optionsを設定すると速くなると書いてあったので試してみました!

環境

  • 2019 iMac
  • Unity 2019.3.2

機能

2019.3から Enter Play Mode Optionsが設定できるようになり、

  • スクリプトの状態のリセット(Domain Reload)
  • シーンの再ロード(Scene Reload)

の2つが設定できるようになりました。

それぞれの詳細機能はドキュメントに書いてありました。 (英語しかなかった・・・)
Unity - Manual: Configurable Enter Play Mode

注意事項

Domain Reloadの設定はstatic変数がリセットされないので気をつけないといけないみたいです。
回避方法はここに載ってました (英語しかなかった・・・)
Unity - Manual: Domain Reloading

Scene Reloadの設定はプロファイルとかビルドとかで計測する場合には当然ですが、正確な時間が計測できないとのことでした。
Unity - Manual: Scene Reloading


あと両者に共通することですが、まだ実験段階の機能なので、どのように変更が将来加えられるかわかりません。

設定方法

Edit > Project Settingから
Editor > Enter Play Mode Settings
f:id:TAKOYAKING:20200224141726p:plain

一番速くておすすめ設定

f:id:TAKOYAKING:20200224141726p:plain
Scene Reload設定のみチェック入れた状態がとても速くておすすめです。
シーンのリロードにチェックをいれないと、シーンのリロードが行われないので、途中から始まるので結構不便です。(個人の感想)
Domain Reloadは行わず、シーンのリロードは行う設定が、とても速くて、使い勝手がよかったです。(今までの操作感と同じ)

ただドキュメントにある通り、速くなる条件はscriptを編集していない時です。
scriptを編集してしまうと、コンパイルが実行されるので、そのコンパイル時間は省略できません。

速くなる状況は例えば

  • UnityEditorでGUIを編集して、確認する (scriptを編集していないため)
  • 停止したあともう一回再生する (scriptを編集していないため)

のような状況が考えられます。

注意点

Domain Reloadのチェックをoffにしているとスクリプトの状態のリセットが行われないので、static変数を初期化する処理を自分でつけないといけないです。

そして、ライブラリについても、アップデートして、対応しているversionに更新しないといけないです。
Enter Play Mode Optionsを設定した時にDoTweenがうまく動作しない時 - TAKOYAKING’s blog

感想

もう速すぎでびっくりしました。最大で 50% ~ 90%短縮と書いてありましたが、体感は99%短縮と言っても過言ではないくらいでした。
とりあえず速すぎたので、再生ボタンを連打しまくるということも、この設定をOnにしていれば楽勝です。
よく再生ボタンを2回連続で押してしまって、微妙にイラっとくることもこれで無くなりそうです。

Unity 2019.3に上げてから少し遅くなり、困っていましたが、これでかなり解決しそうです。

Unityがめちゃくちゃ遅くなるとき

Unityがめちゃくちゃ重くなる時があったのでその解決方法を残しておきます。

環境

  • iMac 2019
  • Unity 2019.2.16f

現象

  • Unity Editorの再生ボタンを押すと始まるまで異様に時間がかかる。(1分以上)
  • ユニットの生成がものすごく時間かかることがあった

解決方法

iMacを再起動する
困った時はPCを再起動・・・

感想

原因がUnityにあるのかiMacにあるのかわかりませんが、再起動するとあれだけ遅かったのが、嘘みたいに速くなりました!
もしかして、PCのメモリがおかしい・・・!?