77
41

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

type coercion(型強制)に慣れ親しむ

Last updated at Posted at 2017-11-18

type coercion(型強制)に慣れ親しむ

はじめに

rust使い始めて2ヶ月くらいなのですが、rust面白いです1。その中で、型変換の解釈が結構むずいなと思いました。

コンパイラのいうこと聞いてそれっぽく直しておけばなんとかなることが大半なのですが、たまに理解できないエラーが出る&納得してコード書きたいので、rustのtype coercion(型強制)についてまとめました。公式ドキュメントにも散らばって記載されている情報なので、間違った所あるかもしれないのですが、指摘してくださればありがたいです!

トピック

rustのtype coercion(型強制)のルールと例をいくつか。

想定する対象者

rustの基本的な文法がだいたいわかるくらいのレベルの人。具体的には、https://rustbyexample.com 内のコードがだいたいわかればオッケーだと思います。

coercionを理解することによって生じる効用

  • 型に関するコンパイルエラーに振り回されずにすむ => めっちゃ快適にコードが書けて、コード書くのが気持ちよくなる:yum:

  • 実行速度的な意味で効率的なコードを書きやすくなる(無駄なコピーとかする場所を減らせたり)

準備

本編に入るための準備事項を記載します。

self

  • syntax sugar
struct T;

impl T {
    fn method1(self) {}
    fn method2(&self) {}
    fn method3(&mut self) {}
} 

fn method2(&self)は実はfn method2(self: &T)のsyntax sugarです2
同様に、fn method1(self: T), fn method3(self: &mut T)となります。

  • Universal Function Call Syntax(UFCS)

receiver: Type(paramter type)として、receiver.method(args)の場合、Type::method(receiver, args)という風にも書き換えられます。特に、TypeTraitであることを強調したい場合は、<Type as Trait>::method(receiver, args)とすれば良いです。この記法をUniversal Function Call Syntax(UFCS)または、Fully Qualified Syntax(FQS)といいます3

deref

derefの基本的な説明については、https://rust-lang-ja.github.io/the-rust-programming-language-ja/1.6/book/deref-coercions.html をみればいいかなと思います。(雰囲気的には&String&strに自動的に変換してくれたりできるのはderefトレイトのおかげですよ、みたいなことを言っている)


さて、上のリンクの中でちょっと気になる点があり、

このルール(著者注: derefによる型強制(deref coercion)) はRustが自動的に変換を行う唯一の箇所の一つです。

ということが書いてありますが、実はちょっと正確じゃないです。4 例えば、

let arr: [i32;5] = [1,2,3,4,5];

let n = arr.first().unwrap(); // 1
// または、
let arr2: Vec<i32> = arr.iter().map(|s| s*2).collect();

みたいなコードを考えます。arrはprimitiveのarray型ですが、arrayのドキュメントを見ても、iterメソッドもfirstメソッドも存在しません(into_iterはありますけど)。
また、このarrayのドキュメントの冒頭にArrays coerce to slices ([T])と書いてありますが、これはどういうことでしょうか?derefを見ても、arrayとDerefトレイトが関係する部分は一つもないですよね。

というわけで、最初は調べれば調べるほど謎だらけでした。こういった、「謎の部分」を解き明かそうというのが本記事の目的です。(ちなみに後半の例にこの例の解釈を書きました)

概要

とりあえず、coercionの概要からまとめたいと思っています。ちょっと形式的なので、退屈に感じる方がいらっしゃるかもです。
なので、実践的な内容から触れたい人は、本記事の「例」から見ていただければ。RFC0401 を中心としたお話です。以降、断りなくTUはparameter typeとします。

そもそも型変換の大きな枠組みとしては、subtyping, coercion, castingがあります。今回詳細にお話するのがcoercionですが、他の2つについて軽く触れておきます:


それでは、coercionについて詳しく触れたいと思います。上記のリンクの他に、https://doc.rust-lang.org/beta/nomicon/coercions.html にも簡潔な説明があります。

coercionの種類

coercionにはいくつか種類があります。

  1. [Deref coercion] Derefトレイトによるcoercion. TDeref<Target = U>を実装していれば、&Tから&Uに型変換される。

  2. [Pointer weakening]

  3. mutabilityが除去されて変換される: &mut T → &Tとか*mut T → *const T

  4. raw pointerに変換される:&mut T → *mut T とか &T → *const T

  5. [Unsizing] &[T; N]から&[T]への変換とか5

また、RFC0401内では、coercionは推移的(transitive)に作用します。つまり、T1からT2へcoerceでき、かつ、T2からT3にcoerceできる状況ならば、T1からT3へのcoercionが可能です。[transitivity case]

注) しかしながら、現時点ではtransitivityは完全に実装されていないそうです。(2017/11/17現在でissue #18602がopenのまま) ここの部分に関して、私自身、最初、例えば、&&&[i32; 5]から&[i32]へなぜ型変換できないのかちょっとわかりませんでした。
(&[i32; 5]から、&[i32]へはunsized coercionの効果で変換できることに注意。また、derefのみに限って言えば、推移的に作用できる。つまり、&&&[i32; 5]から&[i32; 5]へも、二回のdereferenceで型変換に成功する6)。
結果的にstackoverflowで質問してみて、coercionのtransitivity caseに実装が未だ対応していないことが分かったので、よかったら見てください:ok_hand_tone1:

receiver coercion

methodのreceiverの型変換については、特別なcoercion rule(receiver coercion)が適用されます。(receiver.method(args) ↔ Type::method(receiver, args)を思い出しましょう) つまり、receiverからmethodの第一引数(selfとか&selfとかだったり)の型変換には以下の特別ルール7が適用されます:

  1. [by-value-method] methodの第一引数がTだった場合、つまり、method(self: T)だった場合は、このメソッドが使われる(要するに、完全一致の場合はそれ使うよ!ってだけの話です)

  2. [autorefd method] 1で一致しなかった場合は、receiverに&&mutをつけた結果、methodの第一引数の型と一致した場合、つまりmethod(self: &T) or method(self: &mut T)だった場合は当てはまった方のmethodが使われる。

  3. [auto-dereferencing rule] 1,2でもマッチしなかった場合は、前節の[Deref coercion]か[Unsizing coercion]が適用される。

  4. 3を複数回適用した後に、1,2で条件がマッチするならば、1か2に戻って、型が一致する。(ここで、transitive(推移的)との兼ね合いについて気になった方は、脚注の8を読んで下さい)

  5. 1~4によって型の一致がおこらなかった場合は、コンパイルエラーになる。

はい。以上がcoercionのルールとなります。これでわかった方は天才なので:sunglasses:、お待ちかねの例で確認していきます。

以下では、なるべく簡単な例から徐々に解釈の難しい例題にステップアップして、coercionのルールの様相をつかみたいなと思います。coercionの働く例と、coercionと関係しそうだがそうでない例 の構成です。なるべく実践に出てきそうなコードで確認していきたい。

autorefd method

fn main() {
    let s = "abc".to_string(); // let s: String;
    let _n = s.len(); //let n: usize = 3;
}

何の疑問も無さそうなコードですが、敢えて深掘りしましょう。lenメソッドの第一引数は&selfなので、s: Stringと型が合わないです。(selfの節のsyntax sugarの部分からself: &String&selfは同値なのでした)
前章のルールと照らし合わせれば、receiver coercionの[autorefd-method]が適用されることで、https://doc.rust-lang.org/std/string/struct.String.html#method.len が実行されます。
ここで大事なポイントとしては、わざわざ、let _n = (&s).len();のようにする必要はない、ということです(もちろん&つけても、receiver coercionの[by-value-method]が適用され、うまくいく)。

auto-dereferencing rule

fn main() {
    let s = "abc\nあああ\nbcd\n".to_string(); // String
    let buf = s.lines().collect::<Vec<_>>(); // let buf: Vec<&str>
}

これも似たような感じですが、まずreceiver coercionの[auto-dereferencing rule]が適用されるところが違います。(linesメソッドはhttps://doc.rust-lang.org/std/string/struct.String.html#method.lines の通り、strでしか定義されていないメソッドです) つまり、

         3        2
`String`===>`str`===>`&str`

のようにcoerceされます9(番号はreceiver coercionのルールの番号に対応しています)

Deref coercion

fn len_str(s: &str)-> usize {
    s.len()
}

fn main() {
    let s = "abc".to_string(); // String
    let _n = len_str(&s); // 3
    //let _n = s.len(); //let n: usize = 3;
}

selfが関係しないただの関数の場合。
sはString型ですが、通常のcoercionの中の1の[Deref coercion]が作用されて、&String&strとなり、len_strが実行されます。

array coerced to slice

一番はじめに問題提起した例を見てみます:

fn main() {
    let arr = [1,2,3,4,5]; // [i32;5]
    
    let n = arr.first().unwrap(); // 1
    // あるいは以下の例:
    // https://doc.rust-lang.org/std/primitive.i32.html#impl-Mul<i32>-1 があるので、s: &i32と2: i32の掛け算ができる
    let arr2: Vec<i32> = arr.iter().map(|s| s*2).collect();
}

[i32;5]にfirstやiterメソッドは存在しないので、receiver coercionの[auto-dereferencing rule]が適用されます。
つまり、

          3          2
`[i32;5]`===>`[i32]`===>`&[i32](slice)`

の変換が行われ、slice型には,firstiterがあり、これらのメソッドの第一引数は&selfなので、型一致を確認します。

& mut

use std::io::{self, BufReader, BufRead};
fn main() {

    let data = "city\tcountry\tpopulation";
    let mut reader = BufReader::new(data.as_bytes());
    
    let mut line = String::new();
    // fn read_line(&mut self, buf: &mut String) -> Result<usize>
    let len = reader.read_line(&mut line).unwrap();
}

これまたよくある例。receiverであるreader変数はBufReader<io::Stdin>型ですが、receiver coercionの[autorefd method]により、&mutが付加され、read_lineメソッドの第一引数であるself: &mut BufReader<io::Stdin>と一致します。(なので、let len = (&mut reader).read_line(&mut line).unwrap();とかしなくていいです。)
一方、read_lineの第2引数も&mutですが、こちらは&mut lineのように、&mutを明示的に付ける必要があります。(&だけつけるのはだめ) なぜなら、receiverの型変換ではないため、[autorefd method]のルールが適用されず、かといって、derefでサポートされる範囲では&Stringから&mut Stringへの変換もできないからです。

ちなみに、reader変数にはmutが必要です。[autorefd method]が働くと言っても、immutableなものをmutableに変更することはできません:

fn main() {
    let x: i32 = 4;
    // let y: &mut i32 = &mut x; // cannot borrow immutable local variable `x` as mutable

    let mut x: i32 = 4;
    // expected type `&mut i32`, found type `&i32`
    // let y: &mut i32 = &x; // Error: types differ in mutability: 
    let y: &mut i32 = &mut x; // mutable borrow occurs here
}

IndexとRangeFull

お次は、たまによく見る&v[..]&vの違いについてです。正直初見では、読まなくて良い小節ですが(Deref coercionが作用しない例の節まで飛んでください)、フルに上記のcoercionのルールを使うため、敢えて触れておきます。

ちなみに、RangeFullとは、..(double ellipsis)のことです10。よく文字列で&v[..]のような書き方をしますが、簡単のため、整数のスライスで違いを確認していきます。(文字列の場合でも、同じように定義を辿っていけば、考え方は同じです11)

fn func(slice: &[i32])->i32 {
    slice.iter().sum()
}

fn main() {    
    let buffer: &&[i32; 4] = &&[0,1,2,3];
    
    let m: i32 = buffer.iter().sum(); // 6
    //let m: i32 = func(buffer); // Error: expected slice, found &&[i32; 4]
    let m: i32 = func(&buffer[..]); // 6
}

ちょっと意地悪な例を作ってみました。実は、真ん中の例がエラーになり、後の2つはOKとなります。coercionの知識がないと、この挙動を真に理解するのはむずい:sweat:
なんでこういうことが起こるのかを今から説明したいです。


まず一番上の例(let m: i32 = buffer.iter().sum();)から。これは、array coerced to sliceの例とほぼほぼ同じで、以下のようにcoerceされるからです:

[receiver coercionの起こる手順(番号はreceiver coercionの節のルールに対応)]

            3             3          1
`&&[i32;4]`===>`&[i32;4]`===>&[i32]`===>`&[i32]`

iterself: &[i32]なので、これで型一致がなされて、正常に処理されます。


2番目の例(let m: i32 = func(buffer))に行きましょう。これは、selfが絡んでこないので、通常のcoercionのルールが適用されます。。なぜ、型変換できないのか?それは、[transitivity case]の注)に書いた未実装のお話と絡みます。

[coercionの起こる手順]

          Deref         UnSizing    
`&&[i32;4]`===>`&[i32;4]`- - -?>&[i32]

これらは、2つの異なるcoercionが絡むので、transitivity case(推移的にcoercionが行われるケース)に相当しますが、注)により、これらの変換は未実装のため、- - -?>のとおり、移動できず、コンパイルエラーになります。
もちろん、let m: i32 = func(&[0,1,2,3])とだったら、問題ないですよね。


さて、最後の例(let m: i32 = func(&buffer[..]))を見ていきます。こちらはうまくいきますが、一体何が起こっているのでしょうか?

実は、Indexのドキュメントを見ると、container[idx]は、 *container.index(idx) ↔ *std::ops::Index::index(&container, idx)であることが書いてあります。
indexメソッドは(arrayではなく、)sliceモジュールの中にあり、そのシグネチャはindex(self: &[T], index: RangeFull) -> &[T]です。(なので、&buffer[..]のように&が必要12。)

さて、func(&buffer[..]);の引数&buffer[..]について、bufferは&&[i32; 4]です。ここで、bufferはreceiverなので、receiver coercionが働くことに注意すると、&buffer[..]は、以下のようにcoerceされ、indexの第一引数である&[i32]に以下のように型一致します。:

            3             3           1
`&&[i32;4]`===>`&[i32;4]`===>`&[i32]`===>`&[i32]`

ようやく、&buffer[..]の型が&[i32]となることがわかりました。此処から先は自明です。なぜなら、func関数が&[i32]自体を引数に取るので、最後の例が正常に動作するからです。


長くなってしまったので、v: &[T; N]として、&v[..]&vの違いについてまとめておきます。両者は関数の引数が要求する型が&[T]であったときに動作が異なります。

  • &v[..]の方は、引数として与えられる前に(receiver coercionのおかげで)すでに型変換されて&[T]になるので、funcの引数(&[T])と完全一致して問題ない。

  • &vの方は&&[T; N]から&[T]にcoerceしようとしてうまくいかないので、コンパイルエラーになる。


coercionが作用しない例

coercionが発生する場所については、https://doc.rust-lang.org/beta/nomicon/coercions.htmlCoercions occur at a coercion site.の段落で述べられているのですが、ちょっとこれだけではわかりづらいと思います。

そこで逆に考えて、一見coercionが適用されそうだけど、実はそうでない例をあげていきたいと思います。

クロージャー

fn main() {    
    let v: Vec<String> = vec!["abc".to_string(), "cde".to_string()];
    let w: Vec<&String> = v.iter().map(|s| s).collect(); // Vec<&str>とするとエラー
}

ちなみに、wの型をVec<&str>とすると、

error
error[E0277]: the trait bound `std::vec::Vec<&str>: std::iter::FromIterator<&std::string::String>` is not satisfied
 --> src/main.rs:5:47
  |
5 |     let sv2: Vec<&str> = sv.iter().map(|s| s).collect();
  |                                               ^^^^^^^ a collection of type `std::vec::Vec<&str>` cannot be built from an iterator over elements of type `&std::string::String`
  |
  = help: the trait `std::iter::FromIterator<&std::string::String>` is not implemented for `std::vec::Vec<&str>`

となり、コンパイルエラーとなります。
何故かと言うと、mapメソッドの引数の型はFnMut(&String)->&Stringとなっていて、output typeの&String&strにcoercionしないからです。つまり、クロージャーの型推論はcoercionの範疇外であることを示しています。

なので、高階関数を使う際はしっかりクロージャーの型を把握している必要があります。実践でも高階関数内のクロージャーの型不一致でよくコンパイルに注意されますよね:frowning2: 逆に言うと、実践的にはここさえ抑えれば70~80%くらいのコンパイルエラーは回避できるのではないかな、と。

代替案としては、例えば

let w: Vec<&str> = v.iter().map(|s| s.as_str()).collect();
let w: Vec<String> = v.clone().into_iter().map(|s| s).collect(); // もしこの先、vを使わないなら、cloneつけなくて良い
let w: Vec<String> = v.iter().map(|s| s.to_string()).collect();
// let w: Vec<String> = v.iter().map(|s| s.into()).collect(); //Error: &StringはStringに変換できない

とすれば良いですね:v_tone1:

generic type parameter

トレイト内でtype parameterが絡んでいるときも同様にcoercionは働きません:

use std::borrow::Borrow;
// use Borrow trait instead of AsRef because of join method, 
// see also, https://doc.rust-lang.org/std/primitive.slice.html#method.join
pub fn write_lines<S: Borrow<str>>(lines: &[S])->String {
    lines.join("\n")
}

fn main() {
    let s = vec!["aaa", "bbb"]; // vec<&str>
    let res = write_lines(&s); // &strはBorrow<str>トレイトを実装しているので、OK

    let s = vec!["aaa".to_string(), "bbb".to_string()]; // Vec<String>
    let res = write_lines(&s); // StringはBorrow<str>トレイトを実装しているので、"aaa\nbbb"となりOK!

    let (s1, s2) = ("aaa".to_string(), "bbb".to_string());
    let t = vec![&s1, &s2]; // Vec<&String>
    // let res = write_lines(&t); // &StringはBorrow<str>トレイトを実装していないので、Error
}

最後のlet t = vec![&s1, &s2]; // Vec<&String>の例を御覧ください。&String&strにderefしてくれそうですが、以下のエラーが出ます:

error[E0277]: the trait bound `&std::string::String: std::borrow::Borrow<str>` is not satisfied
  --> src/main.rs:14:15
   |
14 |     let res = write_lines(&t);
   |               ^^^^^^^^^^^ the trait `std::borrow::Borrow<str>` is not implemented for `&std::string::String`

これは、traitのtype parameterのマッチのほうが優先され、&Stringstd::borrow::Borrow<str>トレイトを実装していないためです!
解決策として、let t: Vec<&str> = vec![&s1, &s2];と明示的に型を表記してあげれば、先に&s1, &s2変数の型が&String=> &strにderefしてくれるので、大丈夫になります。(根拠としては、https://doc.rust-lang.org/beta/nomicon/coercions.htmllet statements, statics, and consts: let x: U = eと書いてある)


追記) tokioとかのネットワーク関連でよく使われるBytes crateのBytesMutに関して、同類の例を追記。

extern crate bytes;

use bytes::BytesMut;


fn main() {
    let bytes = b"hello world"; // &[u8; 11]
    // Error: the trait `std::convert::From<&[u8; 11]>` is not implemented for `bytes::BytesMut`
    // Fromトレイトが関係しているので、`&[u8; 11]`から`&[u8]`のcoercionは働かない
    //let mut a = BytesMut::from(bytes);

    // &[u8]
    let mut a = BytesMut::from(&bytes[..]);
}
extern crate bytes;

use bytes::{BytesMut, BufMut};


fn main() {
    let mut a = BytesMut::with_capacity(64);
    // Error: the trait `bytes::Buf` is not implemented for `&[u8; 11]`
    // put<T: IntoBuf>(&mut self, src: T)
    // putは引数にIntoBufトレイトの型を要求しているので、`&[u8; 11]>`から`&[u8]`のcoercionは働かない
    // a.put(b"hello world");

    // &[u8]はIntoBufを実装しているので、OK
    a.put(&b"hello world"[..]);
}

一つ目の例は、Fromトレイトが、2つめはInfoBufトレイトが関係しているので、type coercionは起こりません。&b"hello world"[..]に関しては、RangeFullの節でも紹介しましたが、&b"hello world"[..]がまず評価され、&[u8]となります。ここで、&[u8]はInfoBufトレイトを実装している型のため、型一致します。


もう一つ例を。比較演算子の場合です。難易度高めなので、すでにこの時点でキツければ飛ばしてください。

fn main() {
    let s0: &String = &("abc".to_string());
    let flag0 = (s0 == "abc");
    let s1: &&String = &&("abc".to_string());

    let flag1 = (s1 == "abc"); 
}

この時、flag0は評価に成功しますが、flag1の箇所のみコンパイルエラーとなります:

error.log
error[E0277]: the trait bound `&std::string::String: std::cmp::PartialEq<str>` is not satisfied
 --> src/main.rs:6:21
  |
6 |     let flag1 = (s1 == "abc"); 
  |                     ^^ can't compare `&std::string::String` with `str`
  |
  = help: the trait `std::cmp::PartialEq<str>` is not implemented for `&std::string::String`
  = note: required because of the requirements on the impl of `std::cmp::PartialEq<&str>` for `&&std::string::String`

一番下のnote: ..に記載されているとおりなのですが、噛み砕いて説明していきます。

注) ちなみに、&&String->&strへは、coercionが上手くいかないが、&String=>&strへはderef coercionが効くから、ではないです。(つまり、この問題では、transitivity caseが未実装ということと何の関係もないです。)

まずは、前者のlet flag0 = (s0 == "abc")から。

いつもの通り、PartialEqのドキュメント をまず見ましょう13

a == b ↔ a.eq(&b) ↔ <T as PartialEq<Rhs>>::eq(self: &T, other: &Rhs)

であることに注意すると、

// https://doc.rust-lang.org/std/primitive.reference.html#impl-PartialEq<&'b%20B>-1
impl<'a, 'b, A, B> PartialEq<&'b B> for &'a A 
where
    A: PartialEq<B> + ?Sized, // 例の場合、B=strです。
    B: ?Sized, 

および、

// https://doc.rust-lang.org/std/string/struct.String.html#impl-PartialEq<&'a%20str>
impl<'a, 'b> PartialEq<&'a str> for String

なので、impl<'a, 'b> PartialEq<Rhs=&'a str> for &'b Stringが言えます。ということで、s0 == "abc"が評価できる。

一方、後者の方はどう頑張っても、std::cmp::PartialEq<&str> for &&std::string::Stringとはできません。なので、コンパイルエラーになってしまいます。

ということで、coercionとは何の関係もありませんでした。ちなみに、==をスワップして

fn main() {
    let s0: &String = &("abc".to_string());
    let flag0 = ("abc" == s0);
    let s1: &&String = &&("abc".to_string());

    let flag1 = ("abc" == s1); 
}

としても、flag0は評価される一方、flag1の部分で以下のコンパイルエラーが出ます。暇な自由になりたい人は上記と同様の考察をして試してみてください:innocent:

error.log
error[E0277]: the trait bound `str: std::cmp::PartialEq<&std::string::String>` is not satisfied
 --> src/main.rs:6:24
  |
6 |     let flag1 = ("abc" == s1); 
  |                        ^^ can't compare `str` with `&std::string::String`
  |
  = help: the trait `std::cmp::PartialEq<&std::string::String>` is not implemented for `str`
  = note: required because of the requirements on the impl of `std::cmp::PartialEq<&&std::string::String>` for `&'static str`

Note) そうやけど、前提として上みたいな糞コードなんて普通書かんやろって思いませんでしたか? では、以下のコードはどうでしょうか?:

let s0: &String = &("abc".to_string());
assert_eq!(s0, "abc"); // OK

let s1: &&String = &&("abc".to_string());
assert_eq!(s1, "abc"); // Error

assert_eq!は、以下のように定義されています14

macro_rules! assert_eq {
    ($left:expr, $right:expr) => ({
        match (&$left, &$right) {
            (left_val, right_val) => {
                if !(*left_val == *right_val) {
                    panic!(r#"assertion failed: `(left == right)`
  left: `{:?}`,
 right: `{:?}`"#, left_val, right_val)
                }
            }
        }
    });

一度3行目でrefとってから、5行目でdereferenceしているところが違いますが、いずれにしろ比較演算子==を用いているので、実質、上の「糞コード」と同じです。(ちなみに、match文はcoercion site15ではありません。)

error.log
error[E0277]: the trait bound `&std::string::String: std::cmp::PartialEq<str>` is not satisfied
 --> src/main.rs:6:1
  |
6 | assert_eq!(s1, "abc"); // Error
  | ^^^^^^^^^^^^^^^^^^^^^^ can't compare `&std::string::String` with `str`
  |
  = help: the trait `std::cmp::PartialEq<str>` is not implemented for `&std::string::String`
  = note: required because of the requirements on the impl of `std::cmp::PartialEq<&str>` for `&&std::string::String`

同じコンパイルエラーですね:raised_hands_tone1:


追記) また、 BytesMutの例。(こちらのほうがわかり易い例かも..)

extern crate bytes;

use bytes::{BytesMut, BufMut};


fn main() {
    let mut a = BytesMut::with_capacity(64);
    a.put(&b"hello world"[..]);

    // can't compare `bytes::BytesMut` with `[u8; 11]`
    assert_eq!(a, b"hello world");
}

とすると、以下のようなエラーとなります:

error.log
error[E0277]: the trait bound `bytes::BytesMut: std::cmp::PartialEq<[u8; 11]>` is not satisfied
  --> src/main.rs:16:5
   |
16 |     assert_eq!(a, b"hello world");
   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ can't compare `bytes::BytesMut` with `[u8; 11]`
   |
   = help: the trait `std::cmp::PartialEq<[u8; 11]>` is not implemented for `bytes::BytesMut`
   = note: required because of the requirements on the impl of `std::cmp::PartialEq<&[u8; 11]>` for `bytes::BytesMut`

これは、PartialEqトレイトが関係するので、変数a&[u8; 11]から&[u8]にcoercionせずに、型不一致を起こすからです。

一方、assert_eq!(a, &b"hello world"[..]);とすると、第二項の&b"hello world"[..]&[u8]型であり、bytes::BytesMutstd::cmp::PartialEq<&[u8]>を実装しているので、評価に成功します。

まとめ

type coercionをRFC0401に従って説明し、後半は例をあげました。
rustの型変換は思ったより難しいですが、それでもルールをおぼろげにでも把握していれば、rustを書くのがかなり快適になるはずです。後は、わからないエラーが出たときは公式ドキュメントの定義をしっかり確認すれば、本記事の例以外のエラーも自力で解決できることでしょう。

  1. というより、メソッドチェーンが爽快な言語だと思う。

  2. https://github.com/rust-lang/rfcs/blob/73ee34aada32725bda1401dbd1f251e72f5d00d6/text/0401-coercions.md#coercions-of-receiver-expressions を見れば良さげ。後は、 https://rustbyexample.com/fn/methods.html にもcommentに説明が書いてある。

  3. FQSの説明は、https://doc.rust-lang.org/stable/book/second-edition/ch19-03-advanced-traits.html#fully-qualified-syntax-for-disambiguation にある。UFCS自体は、https://doc.rust-lang.org/book/first-edition/ufcs.html#universal-function-call-syntax にある。多分両者は同じ。

  4. そもそも唯一の箇所の一つって、唯一なのかそうでないのかどっちなんだよって感じがする。ちなみに原文はThis rule is one of the only placesでした。

  5. もうちょっとしっかりいうと、TUnsize<U>を実装していて、かつ、Ptr<T>CoerceUnsized<Ptr<U>>を実装しているならば、Ptr<T>Ptr<U>に型変換される。ここで、Ptrとは、例えば、&T, *mut T, Box, RcなどのPointer typeを表す。[T; N]Unsize<[T]>を実装しているので( https://doc.rust-lang.org/std/marker/trait.Unsize.html を参照) &[T; N]&[T]に変換できるというわけです。

  6. たとえば、https://rust-lang-ja.github.io/the-rust-programming-language-ja/1.6/book/deref-coercions.html型がマッチするまで必要なだけ繰り返します。と書いてある

  7. 各名称に関しては、https://doc.rust-lang.org/nomicon/dot-operator.html および、このドキュメントの参照リンクの https://stackoverflow.com/questions/28519997/what-are-rusts-exact-auto-dereferencing-rules/28552082#28552082 を参考にしました。

  8. で、この先めちゃややこしいんですが、このcoercionはマッチするまでのcoercionの回数を1度だけとみなします。例えば、receiverの型が&&[i32; 3]で、methodの第一引数の要求されている型が&[i32]としましょう。この時、&&[i32; 3]=(3)=>&[i32; 3]=(3)=>&[i32]=(1)=>&[i32]のように推移していき型一致を確認しますが、この過程のcoercionを一度きりであるとみなします。つまり、、何が言いたいかというと、普通のcoercionの場合、該当部分に関してtransitive ruleは未実装なので[^attention]、&&[i32; 3]=(3)=>&[i32; 3]までで型変換が終わり(次の&[i32; 3]から&[i32]への変換は2回目に行われるcoercionなので)、型不一致でコンパイルエラーとなってしまいます。しかしながら、このreceiver coercionは、transitive ruleが適用されない状況下にあるので、適切に型一致されます。そこの挙動がよくわからなかったのでこの両者の違いをstackoverflowにて質問してみたのでした。特殊ケースなので通常は意識する必要はないですが、RangeFullの例に登場したりするので、頭の片隅にでも入れておくといつか役立つはずです。

  9. 矢印が複数ありますが、receiver coercionなので、coercionは一回のみなされたとみなします

  10. https://doc.rust-lang.org/std/ops/struct.RangeFull.html を見たほうが早いかも。

  11. https://stackoverflow.com/questions/31797599/what-does-the-double-ellipsis-operator-mean-in-the-context-of-a-str-subscript とかも参考にしてください。

  12. https://doc.rust-lang.org/reference/expressions.html#index-expressions も。詳しく言うと、&container[..]は明示的にカッコを付けると、(&(*(container.index(..)))の順で計算されます。

  13. そもそも、なぜ、Eqトレイトではなく、PartialEqなのかは、例えば、https://qiita.com/lo48576/items/343ca40a03c3b86b67cb とかがわかりやすいと思う。

  14. https://github.com/rust-lang/rust/blob/6a92c0fdbddbda6fcb0c6215f64240d228e4b2f5/src/libcore/macros.rs#L111-L138 参照。後半の($left:expr, $right:expr, $($arg:tt)+)から先の部分は本文で解説していることと同じなので省略。

  15. https://doc.rust-lang.org/beta/nomicon/coercions.html を参照。

77
41
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
77
41

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?