Trait implementation variables - "impl vars"

That's fine, but you'll have to accept that without more details as to what you actually want, this is not happening at all.

Indeed. However, any progress on their part is blocked on questions about what you expect to happen in certain cases. If you cannot explain at least what might happen (not even "should") when your proposed idea is used that satisfies your own goals, I don't know how you can expect anyone else to answer them for you. Again, if this is all you do (and somehow this does get implemented), it'd be frustrating to see "but that's not what I had in mind" if there's some constraint that has to be applied during the implementation.

And yet you can't provide an example of what it looks like before and after and explain the difference? The questions here are not looking for you to answer absolutely everything, but just what you, as the idea originator, are the best person to answer.

All that said, there are a lot of effects here that this idea ends up creating that should really be hashed out up front. Of note (some of which have been asked here):

  • Does adding an "impl var" via impl Trait for T add a field to every type?
  • Can a crate change another crate's type by implementing a trait and adding a field?
  • How does this affect type layout?
  • What the heck happens with an enum?
  • Can I add fields to a ZST? Is it a ZST anymore? What happens to other crates that I depend on (and therefore cannot know about the "impl var") that assume it is (was?) a ZST?

Basically, the core question is:

  • Given a type, where does its "impl var" variables live?

If this cannot be answered, this idea is just not implementable. Of course, there are multiple possible answers for this, but they all have tradeoffs. Some off the top of my head:

Modifies the members of the struct

Pros

  • Simple in concept.

Cons

  • Does not work for types not in the crate (i.e., all top-level types in the impl line must be crate-local and non-generic).
    • Otherwise building becomes undecidable as you need to tell already-compiled crates "oh, this type has additional shadow members you need to consider". Or all types become DynSized which makes Rust removes a lot of its zero-cost abstractions.
  • Does not work for enum.
  • Does not work for ZST.

Store in the impl vtable

Pros

  • Works for all types.
  • Does not modify the "main" type.

Cons

  • Using the trait requires dyn Trait to use so that the extra data can have some per-instance place to actually live.
    • Which means that ty.uses_impl_var() is impossible and instead something like (Box::new(ty) as Box<dyn Trait>).uses_impl_var() is necessary. Note that "impl vars" would somehow have to be initialized in Box::new which means that traits which have "impl vars" need some additional information for Box to know about initializing them. Repeat for other containers which support dyn Trait in them.
    • This may mean that "impl var" is required to be const constructible (otherwise Box::new is of unknown runtime cost, not just "allocate and move")
  • Ambiguities are possible
    • Is self.impl_var a member of the type or the local impl variable of the same name?
    • Syntactic ambiguity: does accessing an "impl var" require different syntax to make it clear that the access has to happen in some other way?
    • What about super-trait "impl vars" of the same name?

As you can see, even these two (but by no means exhaustive) solutions have considerable downsides. Where you come in is helping to clarify what downsides you prefer (if they are at all acceptable in the first place). Otherwise you may be stuck with something that doesn't match what you actually want and everyone is sadder for it.

4 Likes

I'm not here to say it's easy - I know it would likely be a very big change (but overall concept is further hiding certain private variables (further protection) from other methods implemented on the struct - just moving them to a new spot, and out of view of everyone outside of this impl block, but they may be shared across all methods in the block. Lots to think about, I'm sure. Not as easy to "implement" as sounds, and lots of gotchas along the way. I'm just a high-level concept guy.

but here's very minimal example.

struct Foo  { ... some fields ...}
  
impl ICalcTotals for Foo{   // Start of the "impl block"
         partialTotal1: int32 = 0;    // Hi, I'm an "impl var"
         partialTotal2: int32 = 0    // me too
         
       pub calcSomething() {
                // calling methods outside of impl block is OK, they just can't access the vars defined in this impl block
                // You can pass it as a parameter tho of course, but they won't be able to change the value unless the method the parameter is passed to is defined in this impl block.
                 partialTotal1 += some_class.some_method();   
                 partialTotal2 += calc_partialTotal2();   
        }

        pub update_full_total(){
               this.total = partialTotal1 + partialTotal2;
       }
 }

You can already achieve the same privacy by using modules:

struct Foo {
    partial: partial::Partial,
    total: i32,
}

mod partial {
    pub(super) struct Partial {
        // private to mod partial
        total1: i32,
        total2: i32,
    }

    impl super::Foo {
        pub fn calc_something(&mut self) {
            self.partial.total1 += 1;
            self.partial.total2 += 2;
        }

        pub fn update_full_total(&mut self) {
            self.total = self.partial.total1 + self.partial.total2;
        }
    }
}

Let's see if I can pull this out of you, which of these impls should be allowed (if any)?

trait Foo {
    fn foo(&self);
}

trait Bar {
    fn bar(&self);
}

impl Foo for u32 {
    let x: u64 = 10;
    
    fn foo(&self) {
        println!("{}", self.x);
    }
}

impl Foo for std::ops::Range<u32>  {
    let y: &'static str = "hello";
    
    fn foo(&self) {
        println!("{}", self.y);
    }
}

// type from another user-defined crate
impl Foo for ::qux::Qux {
    let z: bool = false;
    
    fn foo(&self) {
        println!("{}", self.z);
    }
}

impl<T> Bar for T {
    let w: u8 = 100;
    fn bar(&self) {
         println!("{}", self.w);
    }
}

Please say the names of the implementor types as written

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.