Slide 1

Slide 1 text

Return-position impl trait in trait (RPITIT) の実 装を雑に見てみる TaKO8Ki

Slide 2

Slide 2 text

Takayuki Maeda / TaKO8Ki Rust committer(compiler contributor team/diagnostic working group) 🦀 @TaKO8Ki @TaKOBKi

Slide 3

Slide 3 text

RPITITとは?

Slide 4

Slide 4 text

RPITITとは?

Slide 5

Slide 5 text

RPITITとは? fn combine_vecs( v: Vec, u: Vec, ) -> impl Iterator { v.into_iter().chain(u.into_iter()).cycle() }

Slide 6

Slide 6 text

RPITITとは? struct Foo { items: Vec } #[derive(Clone)] struct Bar; trait Baz { fn items(&self) -> impl Iterator; } impl Baz for Foo { fn items(&self) -> impl Iterator { self.items.iter().cloned() } }

Slide 7

Slide 7 text

Async fn in trait (AFIT) pub trait Trait { async fn method(&self); }

Slide 8

Slide 8 text

Async fn in trait (AFIT) pub trait Trait { async fn method(&self); } // desugars to pub trait Trait { fn method(&self) -> impl Future; }

Slide 9

Slide 9 text

RPITITがどう実装されている か?

Slide 10

Slide 10 text

ASTからHIRへのlowering fn foo() -> impl Bar と同じように hir::ItemKind::Opaque にloweringさ れる


Slide 11

Slide 11 text

ASTからHIRへのlowering pub struct OpaqueTy<'hir> { pub generics: &'hir Generics<'hir>, pub bounds: GenericBounds<'hir>, pub origin: OpaqueTyOrigin, pub lifetime_mapping: &'hir [(&'hir Lifetime, LocalDefId)], pub in_trait: bool, pub precise_capturing_args: Option<(&'hir [PreciseCapturingArg<'hir>], Span)>, }

Slide 12

Slide 12 text

ASTからHIRへのlowering pub struct OpaqueTy<'hir> { pub generics: &'hir Generics<'hir>, pub bounds: GenericBounds<'hir>, pub origin: OpaqueTyOrigin, pub lifetime_mapping: &'hir [(&'hir Lifetime, LocalDefId)], pub in_trait: bool, pub precise_capturing_args: Option<(&'hir [PreciseCapturingArg<'hir>], Span)>, }

Slide 13

Slide 13 text

ASTからHIRへのlowering let opaque_ty_item = hir::OpaqueTy { generics: this.arena.alloc(hir::Generics { params: generic_params, predicates: &[], has_where_clause_predicates: false, where_clause_span: this.lower_span(span), span: this.lower_span(span), }), bounds, origin, lifetime_mapping, in_trait, precise_capturing_args, }; compiler/rustc_ast_lowering/src/lib.rs#L1738-L1751

Slide 14

Slide 14 text

HIRからtyへのlowering traits/implsに新しい関連型を作ってそれを扱うようにする (hir::TyKind::OpaqueDef をopaque type A = impl B; の代わりに関連型射 影 projectionにloweringする)


Slide 15

Slide 15 text

HIRからtyへのlowering (traits) trait NewIntoIterator { type Item; fn into_iter(self) -> impl Iterator; } // 下のようになる (例) trait NewIntoIterator { type Item; type A: Iterator; fn into_iter(self) -> ::A; }

Slide 16

Slide 16 text

HIRからtyへのlowering (traits) trait NewIntoIterator { type Item; fn into_iter(self) -> impl Iterator; } // 下のようになる (例) trait NewIntoIterator { type Item; type A: Iterator; fn into_iter(self) -> ::A; }

Slide 17

Slide 17 text

HIRからtyへのlowering (impls) impl NewIntoIterator for Vec { type Item = u32; fn into_iter(self) -> impl Iterator { self.into_iter() } } // 下のようになる (例) impl NewIntoIterator for Vec { type Item = u32; type A = impl Iterator; fn into_iter(self) -> ::A { self.into_iter() } }

Slide 18

Slide 18 text

HIRからtyへのlowering (impls) impl NewIntoIterator for Vec { type Item = u32; fn into_iter(self) -> impl Iterator { self.into_iter() } } // 下のようになる (例) impl NewIntoIterator for Vec { type Item = u32; type A = impl Iterator; fn into_iter(self) -> ::A { self.into_iter() } }

Slide 19

Slide 19 text

HIRからtyへのlowering (traits) let trait_assoc_ty = tcx.at(span).create_def(trait_def_id, kw::Empty, DefKind::AssocTy); compiler/rustc_ty_utils/src/assoc.rs#L259

Slide 20

Slide 20 text

HIRからtyへのlowering (impls) let impl_assoc_ty = tcx.at(span).create_def(impl_local_def_id, kw::Empty, DefKind::AssocTy); compiler/rustc_ty_utils/src/assoc.rs#L312

Slide 21

Slide 21 text

HIRからtyへのlowering (impls) を少し深堀り 新しく作る関連型のvisibility (型はprivateなのかpublicなのか) やdefaultnessはどの ように決定すればいいか?


Slide 22

Slide 22 text

HIRからtyへのlowering (impls) を少し深堀り 親の関数から引き継いでくれば良い 
 // Copy visility of the containing function. trait_assoc_ty .visibility(tcx.visibility(fn_def_id)); // Copy defaultness of the containing function. trait_assoc_ty .defaultness(tcx.defaultness(fn_def_id)); compiler/rustc_ty_utils/src/assoc.rs#L280-L285

Slide 23

Slide 23 text

HIRからtyへのlowering (impls/traits) それ以外にもrustc_hir_analysis::collect::generics_of::generics_of など実行されるクエリはいくつかある。


Slide 24

Slide 24 text

RPITITでdynが動かないのは なぜか?

Slide 25

Slide 25 text

RPITITでdynが動かないのはなぜか? RPITITやasync fnはobject safeではないからdynが使えない ref: https://doc.rust-lang.org/reference/items/traits.html#object-safety

Slide 26

Slide 26 text

RPITITでdynが動かないのはなぜか? object-safetyとは、

Slide 27

Slide 27 text

RPITITでdynが動かないのはなぜか? trait NotObjectSafe { const CONST: i32 = 1; // ERROR: cannot have associated const fn foo() {} // ERROR: associated function without Sized fn returns(&self) -> Self; // ERROR: Self in return type fn typed(&self, x: T) {} // ERROR: has generic type parameters fn nested(self: Rc>) {} // ERROR: nested receiver not yet supported }

Slide 28

Slide 28 text

RPITITでdynが動かないのはなぜか? rust-lang/rfcs#255 で定義されているように Trait objectのmethodが呼び出されるタイミングではなく、trait objectが生 成されるときにobject safetyを強制するようになった。これによってTrait objectのobject safetyに関して詳細なエラーが得られるようになった。

Slide 29

Slide 29 text

RPITITでdynが動かないのはなぜか? trait NotObjectSafe { fn foo() {} } struct S; impl NotObjectSafe for S { fn foo() { } } fn main() { let obj: Box = Box::new(S); }

Slide 30

Slide 30 text

RPITITでdynが動かないのはなぜか? error[E0038]: the trait `NotObjectSafe` cannot be made into an object --> src/main.rs:12:14 | 12 | let obj: Box = Box::new(S); // ERROR | ^^^^^^^^^^^^^^^^^^^^^^ `NotObjectSafe` cannot be made into an object | note: for a trait to be "object safe" it needs to allow building a vtable to allow the call to be resolvable dynamically; for more information visit --> src/main.rs:2:8 | 1 | trait NotObjectSafe { | ------------- this trait cannot be made into an object... 2 | fn foo() {} // ERROR: associated function without Sized | ^^^ ...because associated function `foo` has no `self` parameter = help: only type `S` implements the trait, consider using it directly instead

Slide 31

Slide 31 text

RPITITでdynが動かないのはなぜか? error[E0038]: the trait `NotObjectSafe` cannot be made into an object --> src/main.rs:12:14 | 12 | let obj: Box = Box::new(S); // ERROR | ^^^^^^^^^^^^^^^^^^^^^^ `NotObjectSafe` cannot be made into an object | note: for a trait to be "object safe" it needs to allow building a vtable to allow the call to be resolvable dynamically; for more information visit --> src/main.rs:2:8 | 1 | trait NotObjectSafe { | ------------- this trait cannot be made into an object... 2 | fn foo() {} // ERROR: associated function without Sized | ^^^ ...because associated function `foo` has no `self` parameter = help: only type `S` implements the trait, consider using it directly instead

Slide 32

Slide 32 text

RPITITでdynを動かしたい

Slide 33

Slide 33 text

RPITITでdynを動かしたい リリース記事ではtrait-variant crateがdynamic dispatchに対応する予定ら しい。 > We plan to provide utilities that enable dynamic dispatch in an upcoming version of the trait-variant crate. ref: https://blog.rust-lang.org/2023/12/21/async-fn-rpit-in-traits.html

Slide 34

Slide 34 text

RPITITでdynを動かしたい まだdynamic dispatchに対応したバージョンがリリースされていないので対応 方針を軽く探ってみる。 rust-lang/impl-trait-utils#34

Slide 35

Slide 35 text

RPITITでdynを動かしたい pub trait AsyncIter { type Item; async fn next(&mut self) -> Option; async fn size_hint(&self) -> Option; }

Slide 36

Slide 36 text

RPITITでdynを動かしたい pub trait AsyncIter { type Item; // async fn next(&mut self) -> Option; fn next(&mut self) -> impl Future>; // async fn size_hint(&self) -> Option; fn size_hint(&self) -> impl Future>; }

Slide 37

Slide 37 text

RPITITでdynを動かしたい pub trait AsyncIter { type Item; type Next: Future>; // fn next(&mut self) -> impl Future> fn next(&mut self) -> impl Future>; type SizeHint: Future>; // fn size_hint(&self) -> impl Future>; fn size_hint(&self) -> Option; }

Slide 38

Slide 38 text

RPITITでdynを動かしたい pub struct DynAsyncIter <'data, Item> { fatptr: FatPtr<'data, Item>, } trait ErasedAsyncIter { type Item; fn next<'me>(&'me mut self) -> Pin> + 'me>>; fn size_hint<'me>(&'me self) -> Pin> + 'me>>; }

Slide 39

Slide 39 text

RPITITでdynを動かしたい impl<'data, Item> DynAsyncIter<'data, Item> { pub fn new(value: T) -> DynAsyncIter<'data, Item> where T: AsyncIter + 'data, Item: 'data, { let b: Box> = Box::new(value); let raw: *mut dyn ErasedAsyncIter = Box::into_raw(b); unsafe { DynAsyncIter { fatptr: FatPtr::new(raw).tagged(), } } } // .. }

Slide 40

Slide 40 text

RPITITでdynを動かしたい union FatPtr<'data, Item> { raw: *mut (dyn ErasedAsyncIter + 'data), usizes: (usize, usize), } impl<'data, Item> FatPtr<'data, Item> { // ... unsafe fn tagged(self) -> Self { let (data, vtable) = self.usizes; FatPtr { usizes: (data | 1, vtable), } } } usizesフィールドの1番目の要素 としてvtableへのポインタが 入っている

Slide 41

Slide 41 text

RPITITでdynを動かしたい #[trait_variant (dyn)] pub trait AsyncIter { type Item; async fn next(&mut self) -> Option; async fn size_hint(&self) -> Option; } trait_variant(dyn)によってこ れらのtrait/structが実装され るようにするのが現状の方針ら しい

Slide 42

Slide 42 text

まとめ ● ASTから hir::ItemKind::Opaque にloweringされるところから始まり、RPITIT 動きが何となく理解できた。
 ● RPITITやasync fnはobject safeではないのでdynが使えない。 ● dynamic dispatchを使うためには今のところ async-trait proc macro が必要だが、将来的に trait-variant が対応する予定がある。

Slide 43

Slide 43 text

Links ● https://github.com/rust-lang/rust/pull/122103
 ● https://github.com/rust-lang/rust/pull/101224
 ● https://rust-lang.github.io/rfcs/3425-return-position-impl-trait-in-traits.ht ml
 ● https://github.com/rust-lang/rfcs/blob/master/text/0255-object-safety.md
 ● https://github.com/rust-lang/impl-trait-utils/issues/34