Rust では基本的に自動で参照外しが行われ、例えば &&&Foo
型に対して Self 引数 self: &Foo
を持つメソッドが呼ばれたりします。
ところが、ToString
トレイト等は参照型に対してブランケット実装されるため、参照外しせずにメソッドが呼ばれます。
1. 自動参照外しされる場合とされない場合
1.1. 基本的には自動参照外しされる
Rust では基本的に自動で参照外しが行われます。
trait Something {
fn something(&self);
}
struct Foo;
impl Something for Foo {
fn something(&self) {
println!("Foo");
}
}
//
let foo: &&&Foo = &&&Foo;
// 完全修飾構文の関数引数での自動参照外し
<Foo as Something>::something(**foo); // self: &Foo
<Foo as Something>::something(*foo); // 自動参照外し: &&Foo -> &Foo
<Foo as Something>::something(foo); // 自動参照外し: &&&Foo -> &Foo
// <&Foo as Something>::something(*foo); // エラー発生
// <&Foo as Something>::something(foo); // エラー発生
// <&&Foo as Something>::something(foo); // エラー発生
// メソッド探索におけるレシーバの自動参照外し
(**foo).something(); // self: &Foo
(*foo).something(); // 自動参照外し: &&Foo -> &Foo
foo.something(); // 自動参照外し: &&&Foo -> &Foo
※関数引数等での自動参照外しとメソッド探索における自動参照外しは仕組みが異なります (本記事では詳細は不説明) 。
関数引数は型強制サイトのため、自動参照外しされます。
参考「Coercion sites - Type coercions - The Rust Reference」
メソッド呼び出しにおける参照外し等について、詳しくは別記事にしました。
参考「[Rust] メソッド呼び出し時におけるメソッド探索の仕組み: 自動参照 & 自動参照外し及び Unsized 型強制 - Qiita」
1.2. 参照型が実装するメソッドではメソッド探索で自動参照外しされない
参照型にメソッドが実装されている場合は、メソッド呼び出しにおけるメソッド探索で参照外しされる前にメソッドが決定するため、理論上参照外しした場合に一致するメソッドがあったとしても参照外しされません。
※完全修飾構文で呼び出す場合はメソッド探索されず、self
引数でない引数や変数束縛と同様に自動参照外しされます。
trait Something {
fn something(&self);
}
struct Foo;
impl Something for Foo {
fn something(&self) {
println!("Foo");
}
}
impl Something for &Foo {
fn something(&self) {
println!("&Foo");
}
}
impl Something for &&Foo {
fn something(&self) {
println!("&&Foo");
}
}
//
let foo: &&&Foo = &&&Foo;
// 完全修飾構文の関数引数での自動参照外し
<Foo as Something>::something(**foo); // self: &Foo
<Foo as Something>::something(*foo); // 自動参照外し: &&Foo -> &Foo
<Foo as Something>::something(foo); // 自動参照外し: &&&Foo -> &Foo
<&Foo as Something>::something(*foo); // self: &&Foo
<&Foo as Something>::something(foo); // 自動参照外し: &&&Foo -> &&Foo
<&&Foo as Something>::something(foo); // self: &&&Foo
// メソッド探索におけるレシーバの自動参照外し
(**foo).something(); // self: &Foo
(*foo).something(); // self: &&Foo // 自動参照外しされない
foo.something(); // self: &&&Foo // 自動参照外しされない
上記のコードをブランケット実装にすると以下のようになります (&&&&Foo
等にも実装されます) 。
trait Something {
fn something(&self);
}
struct Foo;
impl Something for Foo {
fn something(&self) {
println!("Foo");
}
}
impl<T: Something> Something for &T { // ブランケット実装
fn something(&self) {
print!("&");
Something::something(&**self); // 明示的な参照外し
}
}
// ... メソッド呼び出し略
参考「Using Trait Bounds to Conditionally Implement Methods - Traits: Defining Shared Behavior - The Rust Programming Language」(「ブランケット実装」)
2. 自動参照外しされない標準ライブラリトレイトの例
2.1. Display
トレイトと ToString
トレイト
Display
トレイトは参照 (可変参照も含む) に対して再帰的にブランケット実装され、さらに Display
トレイトを実装する型に対して ToString
トレイトがブランケット実装されます。
use std::fmt;
struct Foo;
impl fmt::Display for Foo {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "Foo")
}
}
//
let foo: &&&Foo = &&&Foo;
// 完全修飾構文の関数引数での自動参照外し
<Foo as ToString>::to_string(**foo); // self: &Foo
<Foo as ToString>::to_string(*foo); // 自動参照外し: &&Foo -> &Foo
<Foo as ToString>::to_string(foo); // 自動参照外し: &&&Foo -> &Foo
<&Foo as ToString>::to_string(*foo); // self: &&Foo
<&Foo as ToString>::to_string(foo); // 自動参照外し: &&&Foo -> &&Foo
<&&Foo as ToString>::to_string(foo); // self: &&&Foo
// メソッド探索におけるレシーバの自動参照外し
(**foo).to_string(); // self: &Foo
(*foo).to_string(); // self: &&Foo // 自動参照外しされない
foo.to_string(); // self: &&&Foo // 自動参照外しされない
ブランケット実装は以下のようにされます。
macro_rules! fmt_refs {
($($tr:ident),*) => {
$(
// ... 略
impl<T: ?Sized + $tr> $tr for &T {
fn fmt(&self, f: &mut Formatter<'_>) -> Result { $tr::fmt(&**self, f) }
}
// ... 略
)*
}
}
fmt_refs! { Debug, Display, Octal, Binary, LowerHex, UpperHex, LowerExp, UpperExp }
impl<T: ?Sized + Display> Display for &T {
fn fmt(&self, f: &mut Formatter<'_>) -> Result { Display::fmt(&**self, f) }
}
impl<T: fmt::Display + ?Sized> ToString for T {
// ... 略
#[inline]
default fn to_string(&self) -> String {
// ... 略
}
}
参考「Display for &'_ T - Display in std::fmt - Rust」
参考「ToString for T - ToString in std::string - Rust」
2.2. Clone
トレイトと ToOwned
トレイト
Clone
トレイトは参照に対して再帰的にブランケット実装され、さらに Clone
トレイトを実装する型に対して ToOwned
トレイトがブランケット実装されます。
Clone
トレイトは Display
トレイトと異なり、可変参照は実装しません。
つまり、可変参照で to_owned
メソッドを呼んだ場合は ToOwned
を実装する型が見つかるまでは自動参照外しおよび自動参照が行われます。
ただし、どんな場合でも完全に参照が取り除かれるわけではありません。
//
let foo: &&i32 = &&23;
let bar = foo.to_owned(); // bar は &i32 型: to_owned(self: &&i32) -> &i32 (foo は自動参照外しされない)
//
let baz: &mut &i32 = &mut &23;
let qux = baz.to_owned(); // qux は i32 型: to_owned(self: &i32) -> i32 (baz は 1 段階自動参照外しされる)
//
let quux: &mut i32 = &mut 23;
let corge = quux.to_owned(); // corge は i32 型: to_owned(self: &i32) -> i32 (quux は自動参照外しおよび自動参照される)
ブランケット実装は以下のようにされます。
impl<T: ?Sized> Clone for &T {
// ... 略
fn clone(&self) -> Self {
*self
}
}
impl<T> ToOwned for T
where
T: Clone,
{
// ... 略
fn to_owned(&self) -> T {
self.clone()
}
// ... 略
}
参考「Clone for &'_ T - Clone in std::clone - Rust」
参考「ToOwned for T - ToOwned in std::borrow - Rust」