HackMD
  • Beta
    Beta  Get a sneak peek of HackMD’s new design
    Turn on the feature preview and give us feedback.
    Go → Got it
    • Beta  Get a sneak peek of HackMD’s new design
      Beta  Get a sneak peek of HackMD’s new design
      Turn on the feature preview and give us feedback.
      Go → Got it
      • Sharing Link copied
      • /edit
      • View mode
        • Edit mode
        • View mode
        • Book mode
        • Slide mode
        Edit mode View mode Book mode Slide mode
      • Note Permission
      • Read
        • Owners
        • Signed-in users
        • Everyone
        Owners Signed-in users Everyone
      • Write
        • Owners
        • Signed-in users
        • Everyone
        Owners Signed-in users Everyone
      • More (Comment, Invitee)
      • Publishing
        Please check the box to agree to the Community Guidelines.
        Everyone on the web can find and read all notes of this public team.
        After the note is published, everyone on the web can find and read this note.
        See all published notes on profile page.
      • Commenting Enable
        Disabled Forbidden Owners Signed-in users Everyone
      • Permission
        • Forbidden
        • Owners
        • Signed-in users
        • Everyone
      • Invitee
      • No invitee
      • Options
      • Versions and GitHub Sync
      • Transfer ownership
      • Delete this note
      • Template
      • Insert from template
      • Export
      • Dropbox
      • Google Drive Export to Google Drive
      • Gist
      • Import
      • Dropbox
      • Google Drive Import from Google Drive
      • Gist
      • Clipboard
      • Download
      • Markdown
      • HTML
      • Raw HTML
    Menu Sharing Help
    Menu
    Options
    Versions and GitHub Sync Transfer ownership Delete this note
    Export
    Dropbox Google Drive Export to Google Drive Gist
    Import
    Dropbox Google Drive Import from Google Drive Gist Clipboard
    Download
    Markdown HTML Raw HTML
    Back
    Sharing
    Sharing Link copied
    /edit
    View mode
    • Edit mode
    • View mode
    • Book mode
    • Slide mode
    Edit mode View mode Book mode Slide mode
    Note Permission
    Read
    Owners
    • Owners
    • Signed-in users
    • Everyone
    Owners Signed-in users Everyone
    Write
    Owners
    • Owners
    • Signed-in users
    • Everyone
    Owners Signed-in users Everyone
    More (Comment, Invitee)
    Publishing
    Please check the box to agree to the Community Guidelines.
    Everyone on the web can find and read all notes of this public team.
    After the note is published, everyone on the web can find and read this note.
    See all published notes on profile page.
    More (Comment, Invitee)
    Commenting Enable
    Disabled Forbidden Owners Signed-in users Everyone
    Permission
    Owners
    • Forbidden
    • Owners
    • Signed-in users
    • Everyone
    Invitee
    No invitee
       owned this note    owned this note      
    Published Linked with GitHub
    Like BookmarkBookmarked
    Subscribed
    • Any changes
      Be notified of any changes
    • Mention me
      Be notified of mention me
    • Unsubscribe
    Subscribe
    # `dyn*` Conversion Traits We want to have a trait or collection of traits to facilitate converting things into `dyn* Trait` objects. Here we discuss some of the desired properties, possible designs, and attempt to settle on the right way to go. ## Design Meeting 2022-10-14 Objectives 1. Decide what version of the traits we want to implement to start with (not necessarily committing to being the long term design) 2. Identify requirements and nice-to-have properties Conclusions: We have choice. To cast `T` to `dyn* Trait` we can require one of the following 1. `T: PointerSized + Trait` 2. `T: PointerSized + Deref*<impl Trait>` Option 1 allows small value inline storage but forces double indirection if `T` is already a pointer. Option 2 does not force double indirection but gives up on the small value inline storage optimization. Avoiding double indirection seems more important than the inline storage optimization. Open Question: Can we get both inline storage and single-indirection using `IntoDyn`? Currently option 1 is implicitly implemented. Do we want to start implementing option 2 instead, or wait until we resolve `IntoDyn`? Option 2 is harder because there are a lot of fiddly rules we'll need to add. Background: - eholk's post on async-fn-in-traits: https://blog.theincredibleholk.org/blog/2022/04/18/how-async-functions-in-traits-could-work-in-rustc/ Questions/Comments: - (tmandry) `IntoDyn` requires us to decide on a per-type basis how to do the conversion. Is that true of the others? - (eholk) You can use adapters like `Boxing`, but that seems like it's actually independent. - (tmandry) Not sure why they would have to be connected. - (tmandry) `IntoDyn` allows for a blanket impl that can be specialized for specific types, but it's not clear if this is an important advantage. - Can we support by-value self? Probably? The vtable should know the size, and `dyn*` is an owned value. `Box<Self>` works currently, and `dyn*` basically functions as a box, so by-value self should work. - Connection to safe transmute - similar because we have the layout and alignment requirement, but we don't need the safety requirements. Safe transmute is one-way (you can go from a reference to an integer, but not back), but we need to be able to go both directions. The compiler guarantees this is safe because it knows it's casting back to the same type. - (tmandry) Pinning - https://rust-lang.github.io/async-fundamentals-initiative/explainer/async_fn_in_dyn_trait/generalizing_from_box_to_dynx.html#the-shim-function-builds-the-dynx - In that example we add an `Unpin` bound on the pointer-sized type to convert it to a `dyn*` *because Future has a `Pin<&mut Self>` method* ### Double Indirection ```rust trait Counter { fn increment(&mut self); } impl Counter for usize { fn increment(&mut self) { *self += 1 } } impl Counter for Box<usize> { fn increment(&mut self) { ... } } // compiler-generated impl Counter for dyn* Counter { fn increment(&mut self) { self.vtable.increment(&mut self.0) } } fn main() { let x = 0usize as dyn* Counter; // X is (0, <usize as Counter>::VTABLE) x.increment(); // desugars to: // <dyn* Counter>::increment(&mut x); // increment gets inlined, but we have to spill x to the // stack to take its address let y = Box::new(0size) as dyn* Counter; y.increment(); // y is (Box(0), <Box<usize> as Counter>::VTABLE) // desugars to: // <dyn* Counter>::increment(&mut y); // // which can be inlined because it's static dispatch to dyn* impl { //<dyn* Counter>::increment(&mut y); //(&mut y).vtable.increment(&mut (&mut y).0) y.vtable.increment(&mut y.0) // &mut (y.0) is a pointer to a pointer } } ``` The double indirection may not be too bad, because the first on is on the stack which is probably in L1. We could avoid the double indirection if we require the first field to always be a pointer and give up on small value inline storage. (tmandry) Is this what was motivating `IntoRawPointer` and `FromRawPointer`? (eholk) What if we require `PointerSized + Deref<impl Trait>` to cast into `dyn* Trait`? - (tmandry) That's enough to get rid of the double indirection - We probably want `DerefMut`, `DerefOwned`, etc. to support different `Self` types --- ## Desiderata Below are some features we might want. - Allow for customizable conversion into a `dyn*` type. For example, many types will want to do this by automatically boxing self, but we want to support other strategies like using an arena allocator or inline storage where possible. - Sensible defaults - Things that are already safe pointers (e.g. `Box`, `Rc`, `Arc`, `&`) should automatically implement the correct traits. - Do we want default impls for things like `usize` and `isize`. It might be better for users to write a newtype wrapper and specify the behavior they want. - Traits can be implemented in Safe Rust - Supports methods with different self types. We at least should support the ones that are already dyn-safe, but if we can support more that would be great. - by-value self types currently don't worth with `dyn Trait`. [[playground](https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=35b42a89584ba378300780674889a628)] - `Arc<Self>` does work: https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=b4a623a6e7edbc835f7e9e3889c5eba3 ## Background: Converting into `dyn*` Let's consider the following program: ```rust= trait Counter { fn get_value(&self) -> usize; fn increment(&mut self); } impl Counter for &mut usize { fn get_value(&self) -> usize { *self } fn increment(&mut self) { *self += 1; } } fn main() { let mut counter = &mut 0usize as dyn* Counter; counter.increment(); println!("The value is {}", counter.get_value()); } ``` To start with, let's assume the compiler does everything by transmuting. Then the compiler might elaborate the program like this: ```rust= // dyn* objects are basically a struct of { data, vtable } // impl automatically generated by compiler impl Counter for dyn* Counter { fn get_value(&self) -> usize { self.vtable.get_value(self.data) } fn increment(&mut self) -> usize { self.vtable.increment(self.data) } } fn main() { let mut counter: dyn* Counter = dyn_star_from_raw_parts( transmute::<*const ()>(&mut 0), VTable { get_value: |this: *const ()| { let this = transmute::<&mut usize>(this); this.get_value() }, increment: |this: *const ()| { let mut this = transmute::<&mut usize>(this); this.increment() } }); counter.increment(); println!("The value is {}", counter.get_value()); } ``` This roughly corresponds to the code that rustc currently generates for `dyn*`. ## Option 1: `PointerSized` This trait would basically signify that the `transmute` into a `*const ()` in the example above would work. Currently the compiler has no verification that it will actually be able to convert a value to a `dyn* Trait` other than making sure the value implements `Trait`. Having a pointer-sized trait would allow us to guarantee at type checking time that the conversion will succeed. `PointerSized` would be a compiler-implemented trait like `Sized`. ## Option 1a: `SameLayout<T>` This is more general and preserves the option for large `dyn*` objects in the future. If we had `SameLayout<T>`, we could implement `PointerSized` as: ```rust= trait PointerSized: SameLayout<*const ()> {} impl<T> PointerSized for T where T: SameLayout<*const ()> {} ``` The main advantage of this version is that it would give us a way to parameterize `dyn*` by a layout and allow more storage space for inline storage. For example, currently you can do `123usize as dyn* Debug` and `123` would be stored in the data field of the `dyn*` object, rather than a pointer to it. If we wanted to allow larger types to work, we could imagine something like `foo as dyn*<[usize; 4]> Debug`, where the data field now uses `[usize; 4]` as its layout and can store up to four `usize` values, and we'd require `foo: Debug + SameLayout<[usize; 4]>` to do the conversion. The ergonomics of using this feature would probably not be great, so it's probably not worth complicating the design too much for what is likely to be a niche use case. Using it in the large would probably be similar to using [StackFuture](https://github.com/microsoft/stackfuture). ## Option 2: Flexible Option - `IntoDyn` / `CoerceSized` In this option, we introduce a trait that allows types to control some of the conversion mechanism. The trait would probably look something like this: ```rust trait IntoDyn { type Ptr: PointerSized; // PointerSized bound is optional if we want to // support larger dyn* objects fn into_dyn(Self) -> Self::Pointer; fn as_ref_self(&Self::Pointer) -> &Self; fn as_mut_Self(&mut Self::Pointer) -> &mut Self; // ...additional conversions for other kinds of self arguments // // These would probably all be split into separate traits // so types would not have to support all self types. } ``` Going back to our example above, the compiler would use these traits when filling in the `dyn*` vtable: ```rust fn main() { let mut counter: dyn* Counter = dyn_star_from_raw_parts( transmute::<*const ()>( <&mut usize as IntoDyn>::into_dyn(&mut 0)), VTable { get_value: |this: *const ()| { let this = transmute::<<&mut usize as IntoDyn>::Ptr>(this); let this = <&mut usize as IntoDyn>::as_ref_self(this); this.get_value() }, increment: |this: *const ()| { let this = transmute::<<&mut usize as IntoDyn>::Ptr>(this); let this = <&mut usize as IntoDyn>::as_mut_self(this); this.increment() } }); counter.increment(); println!("The value is {}", counter.get_value()); } ``` Types would then be responsible for defining how to do this coercion. For things that are already pointer sized, this would likely just be a transmute. In fact, we could provide a blanket implementation: ```rust impl<T> IntoDyn for T where T: PointerSized { type Ptr = T; fn into_dyn(this: Self) -> Self::Ptr { this } // ... } ``` We also have the option of auto-boxing impls: ```rust impl IntoDyn for BigStruct { type Ptr = Box<BigStruct>; fn into_dyn(this: Self) -> Self::Ptr { Box::new(this) } fn as_ref_self(this: &Box<Self::Ptr>) -> &Self { this.as_ref() } // ... } ``` One key point about this design is that the `Ptr` associated type means the compiler can do all of the transmuting in code it generates, and impls of `IntoDyn` (or whatever we call it) can be implemented completely in safe code. ### Is this extra functionality useful? While these traits are significantly more flexible, it's unclear whether that buys us much. For example, the auto-boxing example could be done similarly to this: ```rust trait Foo { async fn foo(&self); } impl Foo for BigStruct { #[refine] fn foo(&self) -> dyn* Future<Output = ()> { Box::new(async { // ... }) } } ``` It's more verbose, but this could be done automatically with macros or trait transformers or some other feature. Because there can only be one impl for a type, the `IntoDyn` trait requires us to decide on a per-type basis how the storage for the result futures is handled. In practice, we may want to make this decision at the call site or somewhere else instead. This could be done using something like `Boxing::new(my_big_struct)`, but that does not require the more complex `IntoDyn` trait. ## Have your cake and eat it too Can we design a set of traits that let us have inline storage and also single indirection? See also tmandry's version at https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=4a3d7f992f2f8d1710e2926ea9914021 ```rust= trait Foo { fn by_value(self); fn by_ref(&self); fn by_mut_ref(&mut self); } impl Foo for usize { ... } trait CoerceDyn { // PointerSized is a compiler-implemented trait that // indicates that a type can be transmuted into *const () type Ptr: PointerSized; fn into_ptr(Self) -> Ptr, fn as_value(&Ptr) -> Self, fn as_ref(&Ptr) -> &Self, fn as_mut_ref(&mut Ptr) -> &mut Self, } // inline storage version impl CoerceDyn for usize { type Ptr = usize; fn into_ptr(x: Self) -> Ptr { x } fn as_value(x: &Ptr) -> Self { *x } fn as_ref(x: &Ptr) -> &Self { x } fn as_mut_ref(x: &mut Ptr) -> &mut Self { x } } // Pointer version, using Box as an example impl CoerceDyn for Box<T> where T: Sized, // so Box<T> is a thin pointer { type Ptr = Box<T>; fn into_ptr(x: Self) -> Ptr { x } // FIXME: as_value needs to take a pointer that // we can deinitialize fn as_value(x: &Ptr) -> Self { *x } fn as_ref(x: &Ptr) -> &Self { x // automatically goes through Deref } fn as_mut_ref(x: &mut Ptr) -> &mut Self { x } } // compiler-generated struct DynStarVTable<Foo> { by_value: fn(*const ()), by_ref: fn(*const ()), by_mut_ref: fn(&mut *const ()), drop: fn(*const ()), } // compiler-generated fn cast_dyn_star<T>(x: T) -> dyn* Foo where T: CoerceDyn { <dyn* Foo> { data: CoerceDyn::into_ptr(x), vtable: &DynStarVTable<Foo> { by_value: fn(this: *const ()) { let this: T::Ptr = transmute(this); let this = <T as CoerceDyn>::as_value(&this); <T as Foo>::by_value(this) } by_ref: fn(this: *const ()) { let this: T::Ptr = transmute(this); let this = <T as CoerceDyn>::as_ref(&this); <T as Foo>::by_ref(this) } by_mut_ref: fn(this_orig: &mut *const ()) { let mut this: T::Ptr = transmute(*this_orig); let result = <T as Foo>::by_mut_ref( <T as CoerceDyn>::as_mut_ref(&mut this)); *this_orig = CoerceDyn::into_ptr(this); result } drop: todo!(), } } } // compiler generated struct <dyn* Foo> { data: *const (), table: &'static DynStarVTable<Foo> } // compiler-generated // // Called by static dispatch essentially always, so LLVM // should pretty much always inline them. impl Foo for dyn* Foo { fn by_value(self) { todo!() } fn by_ref(&self) { todo!() } fn by_mut_ref(&mut self) { self.table.by_mut_ref(&mut self.data) } } ``` ## Open Questions - How do we handle mutable self with inline storage? - Do we even need these? What do we actually need? - Does the trait design influence whether we introduce more memory references in generated code? ## Related Work - [DST Coercions RFC](https://github.com/rust-lang/rfcs/blob/master/text/0982-dst-coercion.md) - [`Pointee` trait](https://doc.rust-lang.org/std/ptr/trait.Pointee.html)

    Import from clipboard

    Advanced permission required

    Your current role can only read. Ask the system administrator to acquire write and comment permission.

    This team is disabled

    Sorry, this team is disabled. You can't edit this note.

    This note is locked

    Sorry, only owner can edit this note.

    Reach the limit

    Sorry, you've reached the max length this note can be.
    Please reduce the content or divide it to more notes, thank you!

    Import from Gist

    Import from Snippet

    or

    Export to Snippet

    Are you sure?

    Do you really want to delete this note?
    All users will lost their connection.

    Create a note from template

    Create a note from template

    Oops...
    This template is not available.


    Upgrade

    All
    • All
    • Team
    No template found.

    Create custom template


    Upgrade

    Delete template

    Do you really want to delete this template?

    This page need refresh

    You have an incompatible client version.
    Refresh to update.
    New version available!
    See releases notes here
    Refresh to enjoy new features.
    Your user state has changed.
    Refresh to load new user state.

    Sign in

    Forgot password

    or

    By clicking below, you agree to our terms of service.

    Sign in via Facebook Sign in via Twitter Sign in via GitHub Sign in via Dropbox

    New to HackMD? Sign up

    Help

    • English
    • 中文
    • Français
    • Deutsch
    • 日本語
    • Español
    • Català
    • Ελληνικά
    • Português
    • italiano
    • Türkçe
    • Русский
    • Nederlands
    • hrvatski jezik
    • język polski
    • Українська
    • हिन्दी
    • svenska
    • Esperanto
    • dansk

    Documents

    Tutorials

    Book Mode Tutorial

    Slide Mode Tutorial

    YAML Metadata

    Contacts

    Facebook

    Twitter

    Feedback

    Send us email

    Resources

    Releases

    Pricing

    Blog

    Policy

    Terms

    Privacy

    Cheatsheet

    Syntax Example Reference
    # Header Header 基本排版
    - Unordered List
    • Unordered List
    1. Ordered List
    1. Ordered List
    - [ ] Todo List
    • Todo List
    > Blockquote
    Blockquote
    **Bold font** Bold font
    *Italics font* Italics font
    ~~Strikethrough~~ Strikethrough
    19^th^ 19th
    H~2~O H2O
    ++Inserted text++ Inserted text
    ==Marked text== Marked text
    [link text](https:// "title") Link
    ![image alt](https:// "title") Image
    `Code` Code 在筆記中貼入程式碼
    ```javascript
    var i = 0;
    ```
    var i = 0;
    :smile: :smile: Emoji list
    {%youtube youtube_id %} Externals
    $L^aT_eX$ LaTeX
    :::info
    This is a alert area.
    :::

    This is a alert area.

    Versions

    Versions and GitHub Sync

    Sign in to link this note to GitHub Learn more
    This note is not linked with GitHub Learn more
     
    Add badge Pull Push GitHub Link Settings
    Upgrade now

    Version named by    

    More Less
    • Edit
    • Delete

    Note content is identical to the latest version.
    Compare with
      Choose a version
      No search result
      Version not found

    Feedback

    Submission failed, please try again

    Thanks for your support.

    On a scale of 0-10, how likely is it that you would recommend HackMD to your friends, family or business associates?

    Please give us some advice and help us improve HackMD.

     

    Thanks for your feedback

    Remove version name

    Do you want to remove this version name and description?

    Transfer ownership

    Transfer to
      Warning: is a public team. If you transfer note to this team, everyone on the web can find and read this note.

        Link with GitHub

        Please authorize HackMD on GitHub

        Please sign in to GitHub and install the HackMD app on your GitHub repo. Learn more

         Sign in to GitHub

        HackMD links with GitHub through a GitHub App. You can choose which repo to install our App.

        Push the note to GitHub Push to GitHub Pull a file from GitHub

          Authorize again
         

        Choose which file to push to

        Select repo
        Refresh Authorize more repos
        Select branch
        Select file
        Select branch
        Choose version(s) to push
        • Save a new version and push
        • Choose from existing versions
        Available push count

        Upgrade

        Pull from GitHub

         
        File from GitHub
        File from HackMD

        GitHub Link Settings

        File linked

        Linked by
        File path
        Last synced branch
        Available push count

        Upgrade

        Danger Zone

        Unlink
        You will no longer receive notification when GitHub file changes after unlink.

        Syncing

        Push failed

        Push successfully