type coercion(型強制)に慣れ親しむ
はじめに
rust使い始めて2ヶ月くらいなのですが、rust面白いです1。その中で、型変換の解釈が結構むずいなと思いました。
コンパイラのいうこと聞いてそれっぽく直しておけばなんとかなることが大半なのですが、たまに理解できないエラーが出る&納得してコード書きたいので、rustのtype coercion(型強制)についてまとめました。公式ドキュメントにも散らばって記載されている情報なので、間違った所あるかもしれないのですが、指摘してくださればありがたいです!
トピック
rustのtype coercion(型強制)のルールと例をいくつか。
想定する対象者
rustの基本的な文法がだいたいわかるくらいのレベルの人。具体的には、https://rustbyexample.com 内のコードがだいたいわかればオッケーだと思います。
coercionを理解することによって生じる効用
-
型に関するコンパイルエラーに振り回されずにすむ => めっちゃ快適にコードが書けて、コード書くのが気持ちよくなる
-
実行速度的な意味で効率的なコードを書きやすくなる(無駄なコピーとかする場所を減らせたり)
準備
本編に入るための準備事項を記載します。
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)
という風にも書き換えられます。特に、Type
がTrait
であることを強調したい場合は、<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 を中心としたお話です。以降、断りなくT
やU
はparameter typeとします。
そもそも型変換の大きな枠組みとしては、subtyping
, coercion
, casting
があります。今回詳細にお話するのがcoercion
ですが、他の2つについて軽く触れておきます:
-
subtyping
.. lifetimeに関する型チェックと暗黙的な型変換。lifetime'a
が'b
よりも長いlifetimeを持つとき、'a
は'b
のsubtypeという。subtypingのモチベーションも含めた話だと、https://doc.rust-lang.org/book/second-edition/ch19-02-advanced-lifetimes.html#lifetime-subtyping がわかりやすい。referenceとしては、 https://doc.rust-lang.org/reference/subtyping.html とか https://doc.rust-lang.org/beta/nomicon/subtyping.html を参照。 -
casting
..as
を用いた明示的な型強制。詳しくは、https://rust-lang-ja.github.io/the-rust-programming-language-ja/1.6/book/casting-between-types.html#as を参照。
それでは、coercion
について詳しく触れたいと思います。上記のリンクの他に、https://doc.rust-lang.org/beta/nomicon/coercions.html にも簡潔な説明があります。
coercionの種類
coercionにはいくつか種類があります。
-
[Deref coercion] Derefトレイトによるcoercion.
T
がDeref<Target = U>
を実装していれば、&T
から&U
に型変換される。 -
[Pointer weakening]
-
mutabilityが除去されて変換される:
&mut T → &T
とか*mut T → *const T
-
raw pointerに変換される:
&mut T → *mut T
とか&T → *const T
-
[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に実装が未だ対応していないことが分かったので、よかったら見てください
receiver coercion
methodのreceiverの型変換については、特別なcoercion rule(receiver coercion)が適用されます。(receiver.method(args) ↔ Type::method(receiver, args)
を思い出しましょう) つまり、receiverからmethodの第一引数(self
とか&self
とかだったり)の型変換には以下の特別ルール7が適用されます:
-
[by-value-method] methodの第一引数が
T
だった場合、つまり、method(self: T)
だった場合は、このメソッドが使われる(要するに、完全一致の場合はそれ使うよ!ってだけの話です) -
[autorefd method] 1で一致しなかった場合は、receiverに
&
か&mut
をつけた結果、methodの第一引数の型と一致した場合、つまりmethod(self: &T)
ormethod(self: &mut T)
だった場合は当てはまった方のmethodが使われる。 -
[auto-dereferencing rule] 1,2でもマッチしなかった場合は、前節の[Deref coercion]か[Unsizing coercion]が適用される。
-
3を複数回適用した後に、1,2で条件がマッチするならば、1か2に戻って、型が一致する。(ここで、transitive(推移的)との兼ね合いについて気になった方は、脚注の8を読んで下さい)
-
1~4によって型の一致がおこらなかった場合は、コンパイルエラーになる。
はい。以上がcoercionのルールとなります。これでわかった方は天才なので、お待ちかねの例で確認していきます。
例
以下では、なるべく簡単な例から徐々に解釈の難しい例題にステップアップして、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型には,firstやiterがあり、これらのメソッドの第一引数は&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の知識がないと、この挙動を真に理解するのはむずい
なんでこういうことが起こるのかを今から説明したいです。
まず一番上の例(let m: i32 = buffer.iter().sum();
)から。これは、array coerced to slice
の例とほぼほぼ同じで、以下のようにcoerceされるからです:
[receiver coercionの起こる手順(番号はreceiver coercionの節のルールに対応)]
3 3 1
`&&[i32;4]`===>`&[i32;4]`===>&[i32]`===>`&[i32]`
iter
はself: &[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.html のCoercions 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[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の範疇外であることを示しています。
なので、高階関数を使う際はしっかりクロージャーの型を把握している必要があります。実践でも高階関数内のクロージャーの型不一致でよくコンパイルに注意されますよね 逆に言うと、実践的にはここさえ抑えれば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に変換できない
とすれば良いですね
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のマッチのほうが優先され、&String
がstd::borrow::Borrow<str>
トレイトを実装していないためです!
解決策として、let t: Vec<&str> = vec![&s1, &s2];
と明示的に型を表記してあげれば、先に&s1
, &s2
変数の型が&String
=> &str
にderefしてくれるので、大丈夫になります。(根拠としては、https://doc.rust-lang.org/beta/nomicon/coercions.html にlet 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[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
の部分で以下のコンパイルエラーが出ます。暇な自由になりたい人は上記と同様の考察をして試してみてください
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[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`
同じコンパイルエラーですね
追記) また、 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[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::BytesMut
はstd::cmp::PartialEq<&[u8]>
を実装しているので、評価に成功します。
まとめ
type coercionをRFC0401に従って説明し、後半は例をあげました。
rustの型変換は思ったより難しいですが、それでもルールをおぼろげにでも把握していれば、rustを書くのがかなり快適になるはずです。後は、わからないエラーが出たときは公式ドキュメントの定義をしっかり確認すれば、本記事の例以外のエラーも自力で解決できることでしょう。
-
というより、メソッドチェーンが爽快な言語だと思う。 ↩
-
https://github.com/rust-lang/rfcs/blob/73ee34aada32725bda1401dbd1f251e72f5d00d6/text/0401-coercions.md#coercions-of-receiver-expressions を見れば良さげ。後は、 https://rustbyexample.com/fn/methods.html にもcommentに説明が書いてある。 ↩
-
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 にある。多分両者は同じ。 ↩
-
そもそも
唯一の箇所の一つ
って、唯一なのかそうでないのかどっちなんだよって感じがする。ちなみに原文はThis rule is one of the only places
でした。 ↩ -
もうちょっとしっかりいうと、
T
がUnsize<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]
に変換できるというわけです。 ↩ -
たとえば、https://rust-lang-ja.github.io/the-rust-programming-language-ja/1.6/book/deref-coercions.html に
型がマッチするまで必要なだけ繰り返します。
と書いてある ↩ -
各名称に関しては、https://doc.rust-lang.org/nomicon/dot-operator.html および、このドキュメントの参照リンクの https://stackoverflow.com/questions/28519997/what-are-rusts-exact-auto-dereferencing-rules/28552082#28552082 を参考にしました。 ↩
-
で、この先めちゃややこしいんですが、この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の例に登場したりするので、頭の片隅にでも入れておくといつか役立つはずです。 ↩ -
矢印が複数ありますが、
receiver coercion
なので、coercionは一回のみなされたとみなします ↩ -
https://doc.rust-lang.org/std/ops/struct.RangeFull.html を見たほうが早いかも。 ↩
-
https://stackoverflow.com/questions/31797599/what-does-the-double-ellipsis-operator-mean-in-the-context-of-a-str-subscript とかも参考にしてください。 ↩
-
https://doc.rust-lang.org/reference/expressions.html#index-expressions も。詳しく言うと、
&container[..]
は明示的にカッコを付けると、(&(*(container.index(..)))
の順で計算されます。 ↩ -
そもそも、なぜ、Eqトレイトではなく、PartialEqなのかは、例えば、https://qiita.com/lo48576/items/343ca40a03c3b86b67cb とかがわかりやすいと思う。 ↩
-
https://github.com/rust-lang/rust/blob/6a92c0fdbddbda6fcb0c6215f64240d228e4b2f5/src/libcore/macros.rs#L111-L138 参照。後半の
($left:expr, $right:expr, $($arg:tt)+)
から先の部分は本文で解説していることと同じなので省略。 ↩ -
https://doc.rust-lang.org/beta/nomicon/coercions.html を参照。 ↩