As I'm laying here, I realize I can succinctly explain how I view and internalize this:
pub makes something visible within the declared module (or anywhere if just pub)
mut makes something mutable within the declared module (or anywhere if just mut)
If omitted entirely, mut inherits its module from pub.
I think these three simple rules are sufficient to define behavior. Moreso, I believe it is clear and simple, and as such is easy to teach. These same rules would apply to impl and the ability to implement a trait.
Should the mut scope be allowed to be larger than the pub scope? This would provide for write-only access, which is unusual but might be useful in some niche cases.
pub fn into_boxed_slice(mut self) -> Box<[T], A> {
unsafe {
self.shrink_to_fit();
let me = ManuallyDrop::new(self);
let buf = ptr::read(&me.buf);
let len = me.len();
buf.into_box(len).assume_init()
}
}
There is always a lot of unsafe implementations under a safe function. crate owner modify it safely since they could guarantee the conditions met. Outside the module, there is no such guarantee.
It seems weird to define a field with pub(crate) mut(anywhere). If we define a field with mut, it should be access safely anywhere, otherwise we should define it as a private field.
Write-only seems illeagal now.
since we could convert &mut T to &T without any restriction.
Semantically? I think it should be a warn-by-default lint. You still wouldn't be able to write to the field because it's not visible. Having a mut scope larger than a pub scope is meaningless, basically. It's the same concern as priv-in-pub.
At the risk of being rude, that's simple not how unsafe works. The fact that you need to guarantee the conditions are met is why it needs to be unsafe. This is true even within the most local of code. But this is geting quite a bit off-topic, so I'll cease responding on this point.
Certainly. Worth noting that right now pub(self) is the same thing as no visibility, so it's not entirely unprecedented. That's where I definitely want a lint to say "this is stupid, why are you doing this?"
This isn't accurate. At a minimum, both pub(extern) mut(self) and pub(crate) mut(self) are commonly useful.
The former will be more common, as hiding the existence of a field isn't strictly necessary if it can't be mutated, but it's still useful in cases where you do want to not promise a field exists for API evolution purposes (e.g. making it a "computed field" in the future [1]; e.g. Vec changing from (ptr: *mut T, cap: usize, len: usize) to (front: *mut T, write: *mut T, back: *mut T)).
So why not just add computed fields / field syntax getters? The reason is all of the differences between proper fields and methods. Even if you handwave partial borrowing, it's the lifetime difference between &vec.len and &vec.len(). ↩︎
In this case, why not just make it pub(crate) mut(crate) or pub(self) mut(self)? Is there any advantages that perfers pub(crate) mut(self) to pub(crate) mut(crate)?
IMHO, when the API evolution happens, all the code written under pub(crate) mut(self) must be rewritten since the field does not exist then. Why we just not provide the "computed field" API to expose that field to the crate (and perhaps, the user)?
If you would like to discuss this, please crate a new thread. I created this thread to work on the syntax previously described. Nothing more, nothing less.
The exact same reason that you would want to not allow users outside the crate to mutate the field: encapsulation. This is more important when safety critical, but still useful even when not.
I touched on the reasons in the footnote, but to reiterate: field references have the source object's lifetime, computed value references have an extended temporary lifetime. It's also unrelated.
Is this sufficiently clear? I'm considering including something almost identical in the RFC itself. This question is particularly directed towards those that didn't find my proposed syntax clear in the first place.
If there is a different syntax that you feel I'm overlooking, please speak up! It's a tradeoff, but I don't find any of the other proposed syntaxes sufficient (in that they don't support having both crate and module-local restriction).
Hoping this doesn't add too much noise to the discussion but I put together a syntax in Zulip chat before realizing there had been quite a lot of discussion here.
This was just a strawman but I'm copying it here (with some of the zulip feedback integrated) for consideration when you put together the RFC - I think it's inline with some of the syntaxes y'all have discussed here:
pub x // publicly mutable (for backwards compatibility)
pub mut x // publicly mutable (de-sugared equivalent to pub x)
pub !mut x // publicly immutable (ie. immutable in all contexts)
pub mut(crate) x // public, crate mutable
pub mut(mod) x // public, module mutable
pub mut(init) x // publicly not mutable after init
mut(crate) x // crate mutable
mut(mod) x // module mutable
mut(init) x // not mutable after init
!mut x // Immutable in all contexts
// pub(crate) variants would follow the same pattern. Eg:
pub(crate) mut(init) x
I hope this is helpful. I'm new around here
Edit: mut(crate) x is not equivalent to mut x. Removed the comment to that effect.
Just realizing that init is orthogonal to crate and module mutability. So perhaps these mutability contexts can be chained and listed like this:
pub mut(crate init, mod unsafe) x // public, mutable during init in this crate and mutable at any time inside unsafe blocks in this module
Edit: added unsafe modifier to further flush out the example after reviewing the other ideas here.
Edit #2: const could be expressed as a modifier and module paths could be specified by mut(mod([path?]) [modifiers...]). Eg.
pub mut(crate const) x // mutable only in const contexts in this crate
pub mut(mod(this_crate::super_module) init) x // mutable only during initialization inside of this_cate::super_module
Last edit, promise. Perhaps self could be expressed as:
mut(Self) // Same as original proposal, mutable inside this struct's methods
mut(mod(Self)) // Equivalent to `mut(Self)` - not so sure about this(?)
In what situation would you want to be able to provide an arbitrary value as an initializer but not permit mutation? To me these have to go together: you need to restrict the ability to set an arbitrary value to a field.
Two things here. You wouldn't have realized this unless you dug through an older thread (that is very long), but syntax like pub(crate, mut self) was discussed before. It was determined that it complicates visibility more than it should, such that having them be separate is more appropriate. The other thing — how can something be unsafe only in certain locations? Either it can cause undefined behavior or it can't, no?
Welcome! Just a note with regard to Discourse: you don't need to indicate what was edited. Edit history is public. And I doubt most people ever look at the edit history (I know I don't).
Regarding initialization, that's a great question. Would initializers perhaps be able to replace builder patterns where you want to initialize a bunch of properties and then freeze the struct? I'm not really familiar with initializers though, (I just saw them mentioned in discussion here) are they covered in detail in a separate proposal?
Also yeah, I completely missed that discussion on list syntax - happily the list syntax is very easy to cut!
Ah, yes I was thinking of something closer to partial initialization - which I ended up finding an old 2018 RFC for that looks like it's been postponed indefinitely; RFC: Direct and Partial Initialization using &uninit T by RustyYato · Pull Request #2534 · rust-lang/rfcs · GitHub so I don't think that should factor into anything here. I think the only value to this would in creating partially frozen/immutable structs from struct expressions (instead of via a method or a builder pattern) but to your point, I don't have a concrete use case for that.
Removing mut(init) from the syntax I threw together doesn't affect the meaning of the other mutability options. I think it can also be cleanly removed so the remaining syntax would be along the lines of:
Where valid modifiers are const and unsafe but the discussion around const seems minimal so I have no attachment to keeping it if it's not adding value.
MIRI should only allow SRO tags for the bytes that are readonly (except for initializers and interior mutability)
I still think other initializers should be possible, currently I am working on pinned initialization and I would like these features to be compatible. I am not sure if and when it is going to be implemented with lang support, but without it, a library could not be compatible with this feature.
I think that we should choose some meaning that is used often for no modifiers. A logical choice would be private + readonly, but this contradicts the current behavior (so only with edition possible). I am not sure if this would be the most common usecase. I think we should rather keep the current behavior, so no modifiers = private + mutable and you could add pub/!mut to make the field public/readonly.
We should try to minimize the number of different keywords in front of fields. I think we should experiment and see how many people use the different attributes and if !mut is used, it might be common to only allow mutation in the crate, so that could be a useful default.
Actually, a library could do this even if full immutability is not part of this proposal. It could be done by having a proc macro that accepts a struct definition, outputting the same definition inside a module and use mod_name::*. You'd have to have some manner to construct it as part of the macro input/output, but it's doable without too much effort. You could then have mut(mod) as the modifier and effectively have full immutability.
A bit orthogonal to this issue, but okay. In my opinion that's an uphill battle — it's a major change without major benefit (even in an edition).
Granted, but this goes hand-in-hand with visibility, which is not an attribute.
MIRI tags each pointer with the capabilities. Because you are allowed to modify the byte behind &UnsafeCell<u8>, but not behind &u8. I am proposing that for readonly fields, the bytes that refer to such a field be always marked SRO (shared read only), except for interior mutability. So it would be UB to modify a readonly field using unsafe code (e.g. with raw pointers).
I cannot really follow, what are you proposing? A couple of issues with that:
solutions with proc macros are generally incompatible with the rust binding for the linux kernel (that is where one of the main applications for initialization lie, of course other embedded programs will benefit as well)
it sounds very unergonomic, I have to repeat the struct definition at every initializer invocation?
Yes I am in favor of keeping the current behavior of no modifers = private + mutable
What do you mean by not an attribute? !mut also is not an attribute