Repr(binned) - making padding fully-UB to read/write if you don't own the type

This would definitely be T: !Binned.

(Having bins decay even in arrays would make Vecs faster. Altho it also means you can't fold your fields into the array's bins. And yeah we know we talked about opting out with (T,) but meh. We don't assume we have full creative control over the feature tho, so feel free to choose your own tradeoffs.)

Oh cool, I didn't realize we had committed to that guarantee about array layout!

Meta: I appreciate that you took the time to wrote out a significant amount of text about how this would work, how it compares to other possibilities, and its advantages and drawbacks.

Though, personally I would be more interested in a way to inline struct fields, despite the disadvantages you pointed out. In my opinion they just solve more problems, including:

  • "Struct full of Options"-like use cases, where a struct has multiple enum-typed fields and their tags could be combined into a bitfield.
  • Enabling arbitrary bitfields.
  • Simulating alignment requirements with offsets, like "multiple of four plus two", for structs like:
    struct Foo {
        a: u16,
        b: (u16, u32), // would be nice if this didn't need padding
        c: u32,
    }
    

I don't see why types which are inlined would have to be fully public or why visibility would be involved at all. I also don't believe this would require monomorphizing any code more than it already is. References would be an issue, but they can be made to work to at least some extent. It's true that there may be a bit of overhead when calling non-inlined methods taking references, so binned would be better for use cases that do that frequently, but on the other hand, inlining allows for significantly more space optimizations. And the design I have in mind doesn't require adding a new default trait bound; as backwards compatibility risks go, at most it might cause issues with applying derive macros to structs that opt into their fields being inlined.

2 Likes

You could have methods like

#[inlinable]
struct Foo { ... }

impl Foo {
  fn foo(&mut self) -> &mut NonInlinable { // can't *return* references to inlinable types but that's okay
    ...
  }
}

But this involves creating synthetic methods for everything that inlines these, so e.g.

struct Bar ( #[inline] Foo );

impl Bar {
  fn bar(&mut self) {
    *self.0.foo() = something;
  }
}

would still compile, but would require generating a copy of Foo::foo specific to Bar's inlined instance of Foo. which we would argue is a form of monomorphization.

This quickly stacks up. We're not sure how fast this grows but we wouldn't be surprised if it grew exponentially. Even if, at first, it looks more efficient than our proposal, it really can't be. And if you disallow this kind of composition/delegation, it becomes practically useless in real-world use-cases.

#[inline] applies to the caller:

#[inline]
fn foo() {
}

fn bar() {
  foo()
}

so an #[inline] struct should just be a form of reverse monomorphization:

#[inline] struct Foo(...);
struct Bar(Foo);

one way to "desugar" inline structs is like so:

trait Foo {
  fn get_field_0();
  fn get_field_1();
  ...
}

impl<T: Foo> T { // this would be "given methods", similar to provided methods but not overridable
  ...
}

in fact, we'd even argue inline structs should actually use a mix of struct/enum and trait syntax:

trait struct Foo(i32, u64);
trait enum Bar<T> {
  Some(T),
  None
}

struct Baz(Foo);

fn do_foo<T: Foo>(...) {
}

impl<T: Foo> T {
  ...
}

this clearly shows the monomorphization cost involved, and is likely easier to deal with in the compiler.

still would prefer to have repr(binned) but this trait-struct-as-inline-struct stuff is also exciting.

(also note the lack of dyn! this requires 2021 edition obviously.) cc @comex

sorry, we know this one isn't as clear as the OP, but it's mostly a vague idea of how inline types could work. obviously the compiler would implicitly create types and stuff based on these, so something like:

trait struct Foo(u64, u32);
struct Bar(Foo, u32);

fn foo<T: Foo>(x: &mut T) {
}
fn bar(x: &mut Bar) {
  foo(&mut x.0);
}

would implicitly create an

struct MutFooInBar<'a> {
  a: &'a mut u64,
  b: &'a mut u32,
}

and use it for the &mut x.0.