Closed3
notes for rust-lang/rust#72614
source: https://github.com/rust-lang/rust/issues/72614
これ何
なぜ言及されているコードのコンパイルが通るのかoli-obk大先生が解説してくれたので日本語で噛み砕く
前提
以下のRustコードを考える (playground):
pub trait Foo {
type Bar: std::ops::Add<Self::Bar, Output = Self::Bar> + std::fmt::Debug;
fn bar(self) -> Self::Bar;
}
impl<T: std::ops::Add<T, Output = T> + std::fmt::Debug> Foo for T {
type Bar = T;
fn bar(self) -> Self::Bar {
self
}
}
pub fn foo() -> impl Foo<Bar = impl std::ops::Sub<usize>> {
5usize
}
fn baz<T: Foo>(foo1: T, foo2: T) {
dbg!(foo1.bar() + foo2.bar()); // result: foo1.bar() + foo2.bar() = 10
}
fn main() {
baz(foo(), foo());
}
-
Foo::Bar
型はstd::ops::Add<>
をトレイト境界として要求している -
foo
関数はBar
型をimpl std::ops::Sub<usize>
とするような返り値を持っている- ここで
std::ops::Add
のトレイトを実装していないこの返り値はエラーとされるべき
- ここで
-
baz
関数はFoo
を実装している型を受け取ってAdd
するような中身 -
main
関数ではbaz
関数にfoo関数
を渡している- つまり、
impl Foo<Bar = impl std::ops::Sub<usize>>
を満たす何か - その"何か"に実装されている
bar
メソッドを呼び出している
- つまり、
-
impl Sub
型で宣言されていないトレイト実装がリークしている-
bar
メソッドはFoo
トレイトに実装されているはずなので
-
"Obligation"の説明:
An Obligation represents some trait reference (e.g.,
i32: Eq
) for which the “impl_source” must be found. The process of finding an “impl_source” is called “resolving” theObligation
. This process consists of either identifying an impl (e.g.,impl Eq for i32
) that satisfies the obligation, or else finding a bound that is in scope.
- "Obligation"は
i32: Eq
のような“impl_source”を必須とする、あるトレイト参照 - “impl_source”を見つけるプロセスをObligationを"resolve"すると呼ぶ
- "resolving"はobligationを満たす
impl Eq for i32
のようなimpl
を特定するか、スコープ内にある境界を見つける作業
解説
rustcではfoo
関数の返り値のような、function signatureにあるopaque typesを推論用変数に置き換えている
つまり以下を例に取ると:
pub fn foo() -> impl Foo<Bar = impl std::ops::Sub<usize>> {
5usize
}
-
impl Sub<usize>
は_1: Sub<usize>
という obligation を持つ_1
という変数になる - つまり、
impl Foo<Bar = impl Sub<usize>>
がimpl Foo<Bar = _1>
となる - 1と同様に
impl Foo
は_2: Foo
という obligation を持つ_2
という変数になる - 2と同様に置き換えると
<_2 as Foo>::Bar = _1
(implから) や<_2 as Foo>::Bar: Add<<_2 as Foo>::Bar>
(トレイト定義から) となる
- 関数の返り値を見ると
usize
で、_2
がusize
であることを確かめる必要がある- →
usize: Foo
と<usize as Foo>::Bar = _1
を証明できればよい
- →
これは:
impl<T: std::ops::Add<T>> Foo for T {
type Bar = T;
fn bar(self) -> Self::Bar {
self
}
}
の部分を見ると簡単に証明できる(usize
は元々Add
トレイトを実装しているため上記2つが成り立つ)
- 上記の
impl
から<usize as Foo>::Bar = usize
と分かる - 今あるobligationsは
<_2 as Foo>:: Bar = _1
と_2 = usize
で、つまり<usize as Foo>::Bar = _1
- これにより
_1 = usize
- さらにトレイトから
<_2 as Foo>::Bar: Add<<_2 as Foo>::Bar>
ということがわかっている - これにより
<usize as Foo>::Bar: Add<<usize as Foo>::Bar>
という風にresolveできる - 以上によって
usize: Add<usize>
が成立する
このスクラップは2022/08/09にクローズされました