Rust 1.26新機能おさらい・後編

後編です。

mainがUnit型以外を返せるようになった

main関数が返り値としてResult<(), impl Debug>を返せるようになったようです。

なお、unstable featureとして、std::process::Terminationというtraitがあって、

use std::process::Termination;

impl Termination for Option<()> {
    fn report(self) -> i32 {
        match self {
            Some(())    => 0,
            None        => 1,
        }
    }
}

fn main() -> Option<()> {
    Some(())
}

とも書けるようですが、今バージョンではまだstableで使えないようです。

閉区間が書けるようになった

Haskellユーザーとかに優しいんじゃないかという機能。

fn main() {
    println!("{:?}", (1..3).collect::<Vec<_>>());
    println!("{:?}", (1..=3).collect::<Vec<_>>());
}

の出力は

[1, 2]
[1, 2, 3]

となります。

なお公式を見るとオーバーフローと組み合わせた例が載っててこれはなるほどなぁという感じ。

スライスパターン

今回の変更でぼくが一番好きなのはコレ。まず基本はmatch文で

fn main() {
    let hoge = [1, 2, 3];
    
    match hoge {
        [a, b, 1] | [1, a, b]   => println!("{}, {}", a, b),
        a @ _                   => println!("{:?}", a),
    }
}

みたいなことができます。ここで注意したいのは、あくまでもスライスパターンはリファレンスを渡しているだけだということです。つまり、1つ目の例なら

fn main() {
    let hoge = [1, 2, 3];
    
    match hoge {
        [ref a, ref b, 1] | [1, ref a, ref b]   => println!("{}, {}", a, b),
        ref a @ _                               => println!("{:?}", a),
    }
}

と等価だと思います。新機能である、match時の適切なrefが効いています。

なお、パターンは以下のように変数束縛やif letでも使えるので、

fn main() {
    let hoge = [1, 2, 3];
    let [a, b, c] = hoge;

    if let [1, 2, d] = hoge {
        println!("{}", d);
    }
}

とかもできます。しかし、スライスパターンからmoveはできないので、Copyトレイトを満たさない型に関してはletすることはできません。

#[derive(Debug)]
struct A {
    a: i64,
}

fn main() {
    let hoge = [A { a : 1 }, A { a : 2 }];
    
    // コンパイルエラー
    let [a, b] = hoge;
}

なお、即値での代入はmove扱いになるようです。

#[derive(Debug)]
struct A {
    a: i64,
}

fn main() {
    let [a, b] = {
        let hoge = [A { a : 1 }, A { a : 2 }];
        hoge
    };
    
    println!("{:?}", a)
}

したがって、上の例でも恒等関数を使うことで無理やり代入するようにすることはできます。

#[derive(Debug)]
struct A {
    a: i64,
}

fn id<T>(v: T) -> T { v }

fn main() {
    let hoge = [A { a : 1 }, A { a : 2 }];
    let [a, b] = id(hoge);
    
    println!("{:?}", a)
}

なお、分配束縛パターンでは、束縛対象をconsumeすることができたので、ちょっとここは気持ち悪いです。

#[derive(Debug)]
struct A {
    a: i64,
}

#[derive(Debug)]
struct B {
    a: A,
}

fn main() {
    let hoge = B { a: A { a: 1 } };
    // これはOK
    let B { a: x } = hoge;

    println!("{:?}", x);
}

また、束縛する対象がスライスの場合、すべての長さを尽くさなくてはいけないので、if letmatchでは使えてもletでは使えません。

fn main() {
    let hoge = &(vec![1, 2, 3])[..];
    
    // OK
    if let [1, 2, d] = hoge {
        println!("{}", d);
    }
    
    // NG コンパイルエラー
    let [a, b, c] = hoge;
}

スライスパターンの導入でリスト操作が相当に楽になると思います。本当は上の恒等関数の例のようにスライスパターンによる代入がもっと簡単にできればベターだったのですが…。

Comments

comments powered by Disqus