Behavior is what decides syntax. Motivation is why we do something at all, not why we do something a certain way. Misusing unsafe is not something we should encourage.
This is not misusing.
No matter what you wrote, you could always use unsafe transmute to modify a private field. Set a field to private already makes it read-only, wrote a getter, #[inline(always)]fn field(&self)->&Field{&self.field} is enough for most cases.
For what it's worth, I like the mut(self) style syntax—it's nicely analogous to the preexisting let mut ...
Outside of the module it means exactly the opposite of let mut, the field is not mutable.
If we didn't have to use the mut keyword for this feature, would another keyword indicating immutable fields make sense?
If we do this I don't think we should do it by default, but with a mode switch on the struct (pub cool struct A). A lint to encourage this sounds difficult though, as it's hard to tell that a struct's fields can be immutable given usage in other modules.
Please note that this thread is for bikeshedding syntax. While some side discussion is expected for things that could plausibly impact the syntax chosen, this thread is not for determining whether read-only/restricted fields are the right choice, what should be considered correct, or anything else along those lines. Keep comments about that to the eventual RFC, where concerns can be properly addressed. Discussion about anything other than potential syntax is off-topic. This is why I nuked everything that was originally this comment: I'm not engaging in off-topic discussion.
Right now I'm leaning towards the syntax I originally proposed, but with the mut(mod) extension. I don't find the arguments for pushing the syntax into pub(additional stuff here) convincing, particularly given the concern about breaking $x:vis matchers and visibilities no longer having the same syntax everywhere.
Outside of the module it means exactly the opposite of
let mut, the field is not mutable.If we didn't have to use the
mutkeyword for this feature, would another keyword indicating immutable fields make sense?
Outside of the module pub(self) means that the item is not pub.
pub(in path) mut(in path) are saying that the item is pub/mut within the given path, and priv/immut outside of it.
As much as pub(readonly) or whatever negative syntax is surface level desirable, mut(in path) fits the existing design of pub(in path) better.
Plus, pub() mut() field extends much more clearly to pub() impl() trait, as opposed to some pub(in path, impl in path), requiring another $:trait_vis matcher in addition to the new $:field_vis matcher.
(As an aside, I would support changing $:vis (in future editions) to consume (and error for) any pub(k#word and not just consuming crate/self. Notably, I'd like to allow pub(extern) for the explicit version of pub's default "pub anywhere" behavior. Not that I expect anyone to actually use it, but having it available I think will help to explain the pub(in path) syntax.)
If we want to lean into the "loud syntax for new things" principle, we could require spelling it mut(in crate) initially, with a future extension to allow pub(crate) style shorthand in the future once people are more comfortable with the visibility extent/restriction syntax.
Personally, though, I'd rather the RFC / unstable impl allow the keyword path shorthand, with a "to be resolved during implementation/stabilization" unresolved question for whether the shorthand syntax should be allowed.
I think the most compelling reason to use a different syntax is the observation that specifying mut(self) seems backwards, because the thing you care about is not mut in not self. I think we could provide something more usable if we avoided that specific problem.
Legit question: how is pub different? pub(in path) and mut(in path) have the same restriction semantics; what makes pub(in path) fine but mut(in path) backwards?
Answering this question specifically, the inversion is in that absence of pub(self) implies !pub whereas absense of mut(self) implies mut(*).
mut(self)impliesmut(*)
it seems mut(self) implies pub(everywhere).
since you could not expose a private field, pub(self) directly make the field unchangeable outside the crate.
Exactly this. When people read pub(...) they're looking for "where is it visible", because the default is "it's not visible". When people read pub mut(...) they're looking for "where is it not mutable", because the default is "it's mutable everywhere it's visible".
That makes more sense. You're saying the non inversion comes from the fact that pub(self) mut(crate) is nonsensical, the default is not mut(*) but rather pub(self) mut(*) (which will be a compile error), but either way means externally immutable (not even visible).
My very small suggestion would be to use mut(mod) instead of mut(self). It's not clear that self refers to the module and not to the type itself (struct in this case), while mod would read pretty unambiguously.
Is pub readonly field: ty acceptable as a grammar? Or readonly pub field: ty?
fn items have their async and unsafe modifiers as separate keywords, without parens.
The reason for the parens is that the (in path) is meaningful the same way it is for pub.
Yes, pub(crate) is by multiple orders of magnitude the most used pub(in path), but pub(in path) is also used. I understand that mut(in crate) will also be multiple orders of magnitude more common than mut(in path), and "just be careful" is a somewhat tenable solution within a single crate (if you aren't std level big, anyway, and most crates would split before then), and all of this combines to make readonly as mut(crate) or mut(self) enticing
but it'd be quite sad to lack the ability to mut(in path) even if it's solely there for completion and teaching.
readonly being the only option and mut(crate) is problematic for two reasons:
- people writing
pub readonly field: Type, then getting mutable access within the crate and thinking something is wrong / at worst, that Rust has shirked its correctness guarantees; and worse - making it unusable for "unsafe fields" like
Vec::len, because then it makes the entire crate inside the safety/privacy barrier rather than nicely scoping it just to just the containing module.
I've even written an inline mod to encapsulate fields behind an unsafe setter for the implementation code before. Readonly not allowing setting the privacy/safety barrier any closer than the crate makes it unfit for use in the same crate as unsafe.
Having readonly be mut(mod) instead perhaps eliminate the unsafe concern and somewhat mitigate the learning concern, but the concern about expressive power still remains either way.
That said, I don't think there's a reason that readonly can't be a contextual keyword.
(Avoiding mut(self) is probably a good idea, since there's a likelihood of people assuming it means protected, i.e. available to impls but not otherwise. I'd almost go so far as to suggest deprecating pub(self) for pub(mod) when on associated items to avoid this.)
I assume pub readonly field would allow mutability only within the module, not the whole crate (using the same scopes as pub field (for readonly) and field (for mutability), without introducing a third scope). From safety standpoint it should be like a private field.
Adding a new keyword (readonly) would already be an edition change, so why not go all the way and make the mutability opt in like @toc suggested?
struct Foo {
a: ty, // only read/write in module
mut b: ty, // same as a
pub c: ty, // read anywhere
pub mut d: ty, // read/write anywhere
pub mut(crate) e: ty, // read anywhere, write only in crate
}
So the default is equivalent to pub(self) mut(self) field, which, at least in my code and the libraries I'm using, seems to be the most common case.
Edition migration would be easy to automate—just put a mut on any pub fields with matching visibility (though you'd probably want to configure it).
The biggest issue with this is that existing code, would continue to compile, but with a different meaning. I don't know if there's precedent for that in past editions.
Exactly this. When people read
pub(...)they're looking for "where is it visible", because the default is "it's not visible". When people readpub mut(...)they're looking for "where is it not mutable", because the default is "it's mutable everywhere it's visible".
For what it's worth, rustdoc (without --document-private-items) would never need to show the mut restriction, as it's limited to within the defining crate.
My very small suggestion would be to use
mut(mod)instead ofmut(self).
This is what my current intent is. Though self is a valid path, so mut(in self) would remain valid.
Is
pub readonly field: tyacceptable as a grammar?
Probably? You'd lose the ability to have different levels of restriction, though, which I find quite useful.
"unsafe fields"
I have plans for unsafe fields as a proper thing (safe to get, unsafe to set). So it's probably best to not look at readonly fields as an alternative to unsafe fields, but rather a supplement. pub mut(mod) unsafe T would be a valid field definition in the future, ideally.
Adding a new keyword (
readonly) would already be an edition change
Not necessarily — contextual keywords exist. I don't know what the lang team's feelings are on contextual vs proper keywords in general.
so why not go all the way and make the mutability opt in
In principle, I would be fine with this. I suspect there would be significant pushback to a change like this because it would affect every non-private field definition. The magnitude of the change would be comparable to that of the change to the module system in 2018.
I have two idea about the syntax now.
first is, it is easy to suppose that pub and mut is exclusive, if we have mut(path), it would directly infers that this field is visible everywhere. (This is why I want to ensure the motivation. In most case, pub(path1) mut(path2) is unnecessary.)
second, mut field is very confusing since mut ident already have several different meaning, crate owner might type mut accidently.
Thus, I sugggests the reuse of the unsafe keyword:
struct Foo{
a: ty,
pub b: ty,
pub(path) c: ty, // the meaning of above defination does not change.
unsafe d: ty, // read anywhere, write safely in module, and unsafe write is needed outside the module
unsafe(path) e: ty, // read anywhere, write safely in path, and unsafe write is needed outside the path
}
Since we could not prevent unsafe modification, it is better to expose the unsafe write directly.
If it's unsafe to write outside of a certain module, how could it be safe within the module? Safety has actual requirements — you can't just call things unsafe because you want to.
On a related note to the mut-restricted fields, this discussion implicitly affects impl-restricted traits. While I didn't intend on that (I thought I was just starting a discussion on mut(self) and similar), it's worth noting that pub impl(crate) trait Foo was also syntax I intended on using in a similar manner. Changing the name of the topic to mirror this.