LoginSignup
4

More than 1 year has passed since last update.

[Rust] to_string メソッド等は呼び出し時に自動参照外しされない

Last updated at Posted at 2021-09-28

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 // 自動参照外しされない

ブランケット実装は以下のようにされます。

参照に対する Display トレイト等のブンラケット実装 (マクロ使用)
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 }
参照に対する Display トレイトのブンラケット実装 (※理論上のソースコード)
impl<T: ?Sized + Display> Display for &T {
    fn fmt(&self, f: &mut Formatter<'_>) -> Result { Display::fmt(&**self, f) }
}
ToString トレイトのブンラケット実装
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 は自動参照外しおよび自動参照される)

ブランケット実装は以下のようにされます。

Clone トレイトのブンラケット実装
    impl<T: ?Sized> Clone for &T {
        // ... 略
        fn clone(&self) -> Self {
            *self
        }
    }
ToOwned トレイトのブンラケット実装
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

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
4