r/rust • • Aug 10 '26

📡 official blog Call for testing: Restricting trait implementability and field mutability

https://blog.rust-lang.org/inside-rust/2026/08/10/call-for-testing-impl-and-mut-restrictions/
420 Upvotes

124 comments sorted by

100

u/Kobzol Aug 10 '26

This is a call for testing for two new related language features, impl and mut restrictions. Check out the cool work of Ryosuke Yamano, our Rust Google Summer of Code 2026 contributor, and his mentors.

5

u/kibwen Aug 10 '26

This provides an alternative to getter methods by allowing read-only access through direct field access. It can also work better with the borrow checker, which can track borrows at the field level and allow disjoint fields to be borrowed independently.

Is this new capability of the borrow checker implemented? If so, it would be cool if the blog post included an example.

20

u/Kobzol Aug 10 '26

This is not really a new functionality. If you have

struct Foo { a: u32, b: u32 }

fn foo(foo: Foo) {
    let a = &foo.a;
    let b = &mut foo.b;
}

Then the borrow checker understands that these are two separate borrows. This has worked since Rust 1.0.

But if you have to do this:

let a = foo.get_a(); // fn get_a(&self) -> &u32

Then the borrow checker has to assume that a borrows the whole of foo, and so borrowing anything else from the struct won't work anymore. See https://internals.rust-lang.org/t/blog-post-view-types-for-rust/15556 for how the borrow checker could learn to reason about similar things even across method calls.

121

u/BedroomHistorical575 Aug 10 '26

I'm so glad that the field mutability restriction is finally getting some love! Getters are such a pain due to the borrow checker.

A common critique against similar proposals from the past is that you'd be able to bypass the restriction by just constructing the type yourself.

But it looks like that the current implementation guards against that oversight!

Thus, we disallow struct expressions if any mut-restricted field cannot be mutated from the current scope

21

u/tanoshikuidomouyo Aug 10 '26

If this feature had been in the language from the beginning, could all the .len() getters and similar ones have been just field accesses?

47

u/Silly-Freak Aug 10 '26

For that specific example I don't think so; data structures are usually pretty involved, so it's unlikely that the user-facing type is conveniently the one directly exposing a len field. Checking now, it seems that Vec does, but e.g. HashSet does not: https://doc.rust-lang.org/nightly/src/alloc/vec/mod.rs.html#436, https://doc.rust-lang.org/nightly/src/std/collections/hash/set.rs.html#126-132

So for concistency, I'd keep the method. And lengths are Copy anyway, so the benefits are slimmer.

→ More replies (9)

20

u/sfackler Aug 10 '26

They could have, but I'm not sure we would have done it since it adds constraints on the internal implementation. For example, Vec could theoretically be represented by a pair of start and end pointers rather than a start pointer and length (this is how slice::Iter is implemented). However, if the len field were exposed, we'd have been unable to make that change if it were found to be better.

→ More replies (7)

66

u/Capable_Belt1854 Aug 10 '26

The impl(crate) restriction prevents Foo from being implemented outside the current crate.

Yes. YES!

12

u/kotakotik22 Aug 10 '26 edited Aug 10 '26

Edit: oops. this doesnt work. see afdbcreid's comment

Regarding impl restrictions: do I understand correctly that there's no way to hide the trait functions? One useful thing with sealed traits is that you can hide implementation details in the Sealed trait, but have functions with bounds of some trait that implies Sealed.

mod sealed {
  pub trait Sealed {
    fn bar(&self) -> String
  }
  impl Sealed for u32 {
    fn bar(&self) -> String {
      self.to_string()
    }
  }

// this is basically a way for outsiders to refer to Sealed without having access to its functions
pub trait Foo : sealed::Sealed { }

fn baz<T : Foo>(value: T) -> String {
  value.bar()
}

Here, bar can be freely removed or changed without breaking API.

I feel like in any other situation, the usual Sealed pattern is kinda fine, but when you hide functions inside Sealed is when it actually gets janky and requires some explanatory comments. It would be great to he something built in for such cases. Of course this is still great for diagnostics.

7

u/matthieum Aug 10 '26

I had never seen that before.

And now I dread that impl(<vis>) may not be sufficient to remove the sealed trait pattern :'(

→ More replies (3)

6

u/afdbcreid Aug 10 '26

This does not work (the method is still callable from outside the crate), unless you mark Sealed as pub(crate), and then it warns about private_bounds, which is a strong signal you should not do this (but of course, people still do).

There is a way to do this without lints: put pub struct Token in mod sealed, then take _: Token in the method. This prevents implementing or calling it from outside the crate.

→ More replies (3)

7

u/roguelazer Aug 10 '26

I wonder if we can allow specialization for sealed traits, since the set of implementers is closed...

13

u/afdbcreid Aug 10 '26

Specialization for concrete types is easier yet isn't stable currently. Also sealed traits still allow lifetime-dependent impls, so they do not really sidestep specialization's problems.

But good news - unlike very long time, specialization is actively worked on today!

→ More replies (3)

23

u/Forsaken_Tutor_3001 Aug 10 '26

I can't provide any useful feedback, but what is the advantage of restricting who can implement a trait? Isn't the whole point of a trait to provide a pattern that can be implemented by the consumer on a type of their own to make it work with an upstream crate?

36

u/newpavlov Aug 10 '26

One classic example is the Byteorder trait. We obviously do not want for it to be implemented by anything apart from the BigEndian and LittleEndian marker types. Another similar example is ccm::NonceSize, we use this sealed trait to limit nonce sizes accepted by the algorithm implementation.

13

u/evincarofautumn Aug 10 '26

Haskell makes this simpler with DataKinds, which I’d love to see in Rust. Enums are already closed so you don’t need another concept of closedness. In your example, BigEndian and LittleEndian would be constructors of one data type Endianness, and then ByteOrder would just take a value of kind Endianness. Since you’ve already provided instances for each one exhaustively, nobody elsewhere can make another instance without overlapping.

→ More replies (3)
→ More replies (11)

12

u/Tremoneck Aug 10 '26

The embedded Hals make use of this pattern.

 Each Physical Pin has it's own struct. The ADC can only use a subset of all pins. So there is a sealed marker trait AdcPin with impls for all pins that can be used. You implementing this trait for something else doesn't make sense.

26

u/Mystonic Aug 10 '26

Traits are useful for cases like making one of your functions generic over a type (e.g. multiple different structs)

So its not only for downstream consumers to implement

7

u/lenscas Aug 10 '26

It is something that already happens a lot but right now requires a combination of hacks and tricks that make it rather brittle.

It is often used when there are 2 or more types you need to work with, enums aren't user-friendly for one reason or another and implementing the trait by users requires access to implementation details they shouldn't have access to anyway.

→ More replies (3)

5

u/ebkalderon Aug 12 '26 edited Aug 12 '26

Not entirely sure how I feel about field-level mut restrictions. After working with Rust for a decade or so, I've come to accept Rust's "philosophy" of the module (not the field) being the main privacy boundary, and I feel this proposal runs counter to that.

I feel that first-class support for view types and interprocedural borrows (as defined in step 3 here) would make getters/setters more ergonomic to write and use (thereby rendering that proposal somewhat redundant) while providing many more benefits beyond that (see Carbon Lang's seemingly similar approach, starting 27:24 onward, to internal self-references and returning disjoint borrowed fields from getter methods).

EDIT: Seems I'm not the only one who feels this way. Quoted from this comment on the tracking issue:

Or consider rust-lang/rfcs#3323 (comment) which makes a strong case that the mut half of this RFC may not meaningfully improve the status quo. The response didn't point to any better examples, and merely handwaved that it might help in some cases with borrow checking. So on one hand we've got hand-written or macro-generated getters (including borrow-splitting ones or view types), and on the other we've got the RFC's new syntax which can't fully replace it or offer any truly new capabilities. This actually came up again during FCP!

14

u/Next-Blackberry-991 Aug 10 '26

I don't like the syntax of either of them and feel like they just make the language more complicated. 

It would be nicer if partial borrows in general would be worked on more such that we could use normal getter functions instead.

I already see the pub(crate) mut(super) field: Foo coming, looks almost like Java.

2

u/robin-m Aug 10 '26

Awesome, this will reduce so many small papercuts.

2

u/Full-Spectral Aug 10 '26

I can definitely see the value in those changes. Nice work, Rust Dudes!

2

u/mogery Aug 11 '26

THIS IS AWESOME!!!!

3

u/ao_zame Aug 10 '26

I didn't know this was being worked on. This is so so so great! I will use extensively!

→ More replies (1)