メソッド呼出では、ドット演算子の左辺が self
に適合するよう型変換される。
左辺が self
の型に適合するまで、以下の型変換ルールを再帰的に適用する。
元の型をそのまま試す。
元の型を不変参照した型を試す (自動参照)。
元の型を可変参照した型を試す (自動参照)。
元の型が Deref
を実装するなら、参照を外した型を試す (自動逆参照)。
元の型が固定長配列なら、その全体のスライス型を試す。
仕様を知らないと、予想外の型で処理が進んでしまう。これにより、コンパイルエラーのメッセージが意味不明に思えたり、より悪くコンパイルエラーにならない場合だと、意図したのと別の型で処理が実行される、等の事態が起きてしまう。
関数呼出の記法は、ドット演算子の記法より型変換が弱い。そのため、上記の仕様が関係していると疑われる場合、記法を変えると問題の理解に役立つ可能性がある。
例えば、x.method()
の場合、<SomeStruct>::
や <SomeStruct>::
等を試すとよい (参考: トレイトが絡む場合は <&SomeStruct as SomeTrait>::
のような記法も使える)。
以下のコードでは、前述のどのルールでもエラーになるが、エラーメッセージについては二つ目のルールを基準に出力されている。そのため、他のルールを想定して読んでいると、エラーメッセージの意味が通らなくなる。
一つ目のルールの適用は失敗する。
<[i32] as SomeTrait>::method(arr)
が試される。
つまり、T
は [i32]
になる。
しかし、T
は Sized
に違反する。
※ 型パラメタは暗黙で Sized
境界を持つ。
二つ目のルールの適用も失敗する。
<&[i32] as SomeTrait>::method(&arr)
が試される。
つまり、T
は &[i32]
になる。
ここで、T
は Sized
に適合する。
しかし、T
は 'static
に違反する。
※ Any
は 'static
ライフタイム境界を持つ。
use std::any::Any;
fn main() {
test(&[1, 2, 3]);
}
fn test(arr: &[i32]) {
arr.method();
}
trait SomeTrait {
fn method(&self) {}
}
impl<T: Any> SomeTrait for T {
// NOP.
}
error[E0521]: borrowed data escapes outside of function --> src\main.rs:8:5 | 7 | fn test(arr: &[i32]) { | --- - let's call the lifetime of this reference `'1` | | | `arr` is a reference that is only valid in the function body 8 | arr.method(); | ^^^^^^^^^^^^ | | | `arr` escapes the function body here | argument requires that `'1` must outlive `'static`
以下のコードでは、いくつかの値と型をペアで出力しているが、文字列スライス型でのみ失敗している。これは筆者が誤って文字列スライスを Sized
として処理、コンパイラがその不備を修正するため、型変換でスライス型をスライスの参照型にした結果である。
一つ目のルールの適用は失敗する。
<str as Report>::report("text")
が試される。
つまり、T
は str
になる。
しかし、T
は Sized
に違反する。
※ 型パラメタは暗黙で Sized
境界を持つ。
二つ目のルールの適用は成功する。
<&str as Report>::report(&"text")
が試される。
つまり、T
は &str
になる。
ここで、T
は Sized
に適合する。
use std::any::{Any, TypeId};
use std::collections::HashMap;
use std::fmt::Display;
use std::sync::OnceLock;
fn main() {
(&false as &bool).report();
(&42 as &i32).report();
("text" as &str).report();
}
trait Report {
fn report(&self);
}
impl<T: Any + Display> Report for T {
fn report(&self) {
let type_id = TypeId::of::<T>();
let header = *header_map().get(&type_id).unwrap_or(&"?");
println!("{}: {}", header, self);
}
}
fn header_map() -> &'static HashMap<TypeId, &'static str> {
static MAP: OnceLock<HashMap<TypeId, &'static str>> = OnceLock::new();
MAP.get_or_init(|| {
let mut result = HashMap::new();
result.insert(TypeId::of::<bool>(), "b");
result.insert(TypeId::of::<i32>(), "i");
result.insert(TypeId::of::<str>(), "s");
result
})
}
b: false
i: 42
?: text