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

今更ですがRust 1.26が5/10にリリースされました。新機能をおさらいしましょう。今回は相当に大きい変更がたくさん入っています。公式はここです。

impl Trait

impl Traitは最大の変更のひとつでしょう。ざっくりいうと、型名のようにimpl Traitを使えます。

use std::fmt::Debug;

fn hoge(a: impl Debug) {
    println!("{:?}", a);
}

fn fuga() -> impl Debug {
    1
}

引数のトレイト境界を指定する使い方の場合、従来のジェネリクスとの棲み分けをどうするべきなのかはすこし気になります。まあ、簡単な場合であればおそらくimpl Traitを使うようになるんでしょう。ただ、微妙にトリッキーなのは、こういう場合がコンパイルエラーになることです。

trait A {
    fn a(b: i32) -> i32 {
        b + 1
    }
}

struct B;

impl A for B {}

fn hoge<S: A>(a: impl Debug) {
    println!("{}, {:?}", S::a(1), a);
}

fn main() {
    hoge::<B>(3i32);
}

内部的にimpl Traitはジェネリクスとして扱われているような感じのエラーメッセージが出てきますが、hoge<B, _>(3i32)などどう書いてみてもコンパイルは通らないので気をつけましょう。

返り値をimpl Traitにした場合、これはジェネリクスとは関係なくその型はあくまでも存在型(existential type)であると書かれています-つまり、impl Aなる返り値は、$\exists T$ s.t. $T: A$なる型$T$の略記です。したがって、これはコンパイルエラーです。

use std::fmt::Debug;

fn piyo(a: i64) -> impl Debug {
    if a == 0 {
        1
    } else {
        "a"
    }
}

結局静的ディスパッチなので自明なのですが、初めて触る人には困惑のもとになる気もします。(またRustの敷居が上がってしまった。。。)

また、複数のトレイト境界を要求する場合は+で結合できます。

fn fuga() -> impl Debug + Display {
    1
}

impl Traitでは実際の型が他のトレイト境界を満たしているかどうかに関わらず-これは静的に決まることではありますが-返された値については明記したトレイトのメソッドしか使えないようになっています。つまり、

fn fuga() -> impl Debug {
    1
}

println!("{}", fuga());

とかはダメなわけです。これをしたければ上のように返り値をimpl Debug + Displayとすることになります。Debug境界も満たしていてほしいなあとか思ったりEqとかPartialEqとか言い出したりするとこの指定がおそらくかなり長ったらしくなりそうですがこれはちょっと鬱陶しいところです。

現状匿名構造体みたいな仕組みはないので、impl Traitをしたところで結局構造体を定義する必要はあります。しかし、今までと異なるのは、定義した構造体が必ずしもパブリックでなくても良いという点です。

mod a {
    use std::fmt::Debug;
    
    #[derive(Debug)]
    struct A;
    
    // OK
    pub fn fuga() -> impl Debug {
        A{}
    }
}

mod b {
    use std::fmt::Debug;
    
    #[derive(Debug)]
    struct A;
    
    // NG
    pub fn fuga() -> A {
        A{}
    }
}

Iteratorの気持ち悪い返り値とかがどうにかなるとよいですね。

追記

公式読んでて気が付きましたが、長ったらしい型名を書かなくて良い場合

fn hoge(v: Vec<i64>) -> impl Iterator<Item=i64> {
    v.into_iter()
     .map(|x| x * 2)
     .filter(|x| x + 4 > 10)
}

や、クロージャを返せる場合(クロージャはそもそも匿名の構造体)

fn hoge() -> impl Fn(i64) -> i64 {
    |x| x * 2
}

とかもありました。特に多分クロージャが静的ディスパッチでよくなったのは本質的な改善点ですね。

match時の適切なref/deref

ちょっと個人的には過保護かなあと思う機能。まあ、理解して使う分には便利かと思います。

fn hello(arg: &Option<String>) {
    match arg {
        &Some(ref name) => println!("Hello {}!", name),
        &None => println!("I don't know who you are."),
    }
}

fn hello(arg: &Option<String>) {
    match arg {
        Some(name) => println!("Hello {}!", name),
        None => (),
    }
}

と書けるようになったという話です。まあこれがないとスライスパターンが(´;ω;`)ウッ…ってなることは目に見えているので仕方ないんでしょうか。

長くなってきたので分けます。

Comments

comments powered by Disqus