1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【Rust】よく考えたらこれコンパイル通るのおかしくない?シリーズ

Last updated at Posted at 2025-04-26

Rustの参照と所有権難しいですよね。今回はRustの「暗黙的な参照変換」について解説します。

下記のコードを見てください。よく考えると一貫性がないように見えませんか?

struct Foo { name: String }

impl Foo {
    fn bar(&self) {
        println!("{}", self.name);
    }
}

fn baz(r: &String) {
    println!("{}", r);
}

fn main() {
    let foo: Foo = Foo { name: "some_string".to_string() };
    let foo_ref: &Foo = &foo;
    
    // (1) YES - コンパイル成功
    foo.bar();
    
    // (2) NO - コンパイルエラー
    baz(foo_ref.name);
    
    // (3) NO - コンパイルエラー
    let name = foo_ref.name;
    println!("{}", name);
    
    // (4) YES - コンパイル成功
    println!("{}", foo_ref.name);
    
    // (5) YES - コンパイル成功
    if "hello".to_string() < foo_ref.name {
        println!("x")
    } else {
        println!("y")
    }
}

ドット演算子の自動参照・自動逆参照

Rustにおいて、ドット演算子(.)は自動参照自動逆参照という機能を持っています。この機能は 左側 のオペランドに対してのみ働きます。

ケース(1)のfoo.bar()では、fooFoo型ですが、barメソッドは&selfFooへの参照)を期待しています。ドット演算子の自動参照機能により、fooは自動的に&fooに変換されます。

普段意識しないかもしれませんが、ドット演算子は左側にある値を必要に応じて参照したり、逆参照したりしているんですね。

参照と値の取得

2番目と3番目の例でコンパイルエラーが発生する理由はすぐにわかると思います。foo_ref.nameString型の値を返すからですね。

// (2) NO - コンパイルエラー
baz(foo_ref.name);

しかし、よく考えてみましょう。 先程の自動参照機能で暗黙的に変換してくれそうな気もしませんか?

自動変換はドットの右側の値には適用されないのです。

この場合、baz関数は&String型の参照を期待しているので、明示的に参照を取得する必要があります:

// これなら動作します
baz(&foo_ref.name);

同様に、3番目の例もname変数にString型の値を代入しようとしていますが、実際には参照を通して値をコピーしようとしているため、コンパイルエラーになります。

マクロの特殊な振る舞い

ここで4番目の例をみて 「おや?!」 と思いませんか?

(3)のprintln!("{}", name);はコンパイルできないのになぜprintln!("{}", foo_ref.name);は通るんでしょうか?

これが成功するのは、println!マクロの特殊な振る舞いによるものです。

println!はマクロであり、通常の関数とは異なる振る舞いをします。println!マクロにはフォーマットの過程で必要に応じて参照演算子を追加する処理が備わっているため、コンパイルエラーが発生しません。

比較演算子の動作

5番目の例が成功する理由も、比較演算子の特殊な内部実装にあります。

// (5) YES - コンパイル成功
if "hello".to_string() < foo_ref.name {
    println!("x")
} else {
    println!("y")
}

Rustでは比較演算子を使う際に、内部的にオペランドに対して自動的に参照が取られる仕組みになっています。これにより、String型同士の比較として処理され、コンパイルが成功します。

まとめ

  1. ドット演算子(.)は左側の値に対して自動的に参照・逆参照を行いますが、右側の値(フィールドやメソッドの結果)には適用されません
  2. println!などのマクロは特殊な振る舞いをすることがあり、一般的な関数呼び出しとは異なる参照処理を行います
  3. 比較演算子などの演算子も、内部的に特別な参照処理を行います

以上、知らなくても困らないであろうRustの小ネタでした

参考資料

1
0
0

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
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?