4 stable releases

1.2.1 May 13, 2020
1.2.0 Apr 25, 2020
1.1.1 Apr 11, 2020
1.1.0 Feb 22, 2020
1.0.0 Jan 31, 2020

#331 in Rust patterns

Download history 118/week @ 2021-10-07 114/week @ 2021-10-14 142/week @ 2021-10-21 67/week @ 2021-10-28 126/week @ 2021-11-04 75/week @ 2021-11-11 110/week @ 2021-11-18 96/week @ 2021-11-25 152/week @ 2021-12-02 131/week @ 2021-12-09 100/week @ 2021-12-16 54/week @ 2021-12-23 98/week @ 2021-12-30 296/week @ 2022-01-06 288/week @ 2022-01-13 147/week @ 2022-01-20

830 downloads per month
Used in 20 crates (8 directly)

MIT/Apache

31KB
425 lines

Erase pointers of their concrete type and store type-erased pointers.

This is roughly equivalent to C's void*, but it does not use libc::c_void.

There are two main useful reasons to type erase pointers in Rust:

  • Removing viral generics from internal implementation details. If the internals truly don't care about the stored type, treating it opaquely reduces monomorphization cost both to the author and the compiler.
  • Thin pointers to ?Sized types. If an unsized type stores its metadata inline, then it can implement Erasable and be used behind type-erased pointers. The type erased pointer does not have to carry the metadata, and the fat pointer can be recovered from the inline metadata. We provide the Thin wrapper type to provide thin pointer types.

Changelist

1.2.0

Added

  • impl ErasablePtr for Thin<P>: the obvious impl; Thin is erased internally
  • Thin::ptr_eq: easily compare pointer equality of two thin pointers

1.1.0

Breaking changes

Unfortunately, a subtle problem with the requirements of Erasable::unerase on implementors was found, and needed to be corrected in this version.

It has always been the intent that a pointer of any provenance should roundtrip through Erasable::erase/unerase without any impact on provenance validity of any pointer. Unfortunately, in 1.0.0, Eraseable::unerase explicitly suggested the use of temporary shared references (&_) in its implementation, which do not hold up this desired semantics. To quote Erasable::unerase's new documentation:

No references to the pointee at all should be created, as their mere temporary existence may impact the validity and usable provenance of other pointers to the same location.

Creating a shared reference sounds on the surface like it should be ok. After all, you have a known-valid pointer to your type, and you can borrow from whatever pointer was erased. However, in the face of raw pointers with a shared mutable provenance, this is problematic. If a write to the pointee location even potentially races with any invocation of unerase, and it creates a reference to the location, we have immediate undefined behavior for writing behind a shared ref.

The root issue is that there may be external synchronization that this implementation has no way of knowing about. An implementation of this trait must only read the mimimum amount of data required to re-type the pointer, and must do so with a raw pointer read, or, if and only if there is a known UnsafeCell point (such as an atomic), a reference to that UnsafeCell point and the safe API of that UnsafeCell point.

For more information around the discovery of this issue see this Twitter thread.

To make this update easier, we additionally added const Erasable::ACK_1_1_0: bool. This value defaults to false, but must be overriden to true in new implementations of Erasable as an explicit acknowledgement of the new requirement. To make finding impls that do not do so, the ERASABLE_ENFORCE_1_1_0_SEMANTICS environment variable can be set to remove the default implementation, explicitly breaking any implementor that did not override its value.

If you have a use of unerase that would be made unsound by unerase creating a shared reference, then it is recommended to assert the value of ACK_1_1_0 to guard against old impls that may create a shared reference. Also, please tell me what it is, because I have not been able to construct a reasonable example that would be made unsound by this hole, just a theoretical attack vector. Similarly, if you can show that this isn't required, get in touch. I'd love to remove this hack if it turns out unnecessary after all (but I think I'm confident ruling out that possibility).

Related Crates

  • ptr-union: Pointer unions the size of a pointer.
  • rc-borrow: Borrowed forms of Rc and Arc.
  • rc-box: Known unique forms of Rc and Arc.
  • slice-dst: Support for custom slice-based DSTs.

Minimum Supported Rust Version

We require a minimum Rust version of 1.41.0. This is for an adjustment of local trait impl checking.

Minimum version support is only guaranteed with minimal version resolution (-Z minimal-versions/--minimal-versions) due to how dependencies are handled. The minimum version of Rust will only be incremented with minor version bumps, not patch version bumps, and will be deliberate and clearly noted in change notes.

License

Licensed under either of

at your option.

If you are a highly paid worker at any company that prioritises profit over people, you can still use this crate. I simply wish you will unionise and push back against the obsession for growth, control, and power that is rampant in your workplace. Please take a stand against the horrible working conditions they inflict on your lesser paid colleagues, and more generally their disrespect for the very human rights they claim to fight for.

Contribution

Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions.

Dependencies

~19KB