# \[Pre-RFC\] read-only visibility

**URL:** <https://internals.rust-lang.org/t/pre-rfc-read-only-visibility/11280>\
**Category:** language design\
**Created:** [November 13, 2019, 12:32am UTC](https://internals.rust-lang.org/t/pre-rfc-read-only-visibility/11280 "2019-11-13T00:32:20Z")\
**Posts on this page:** 20\
**Page:** 1

<div class="post-metadata">

**Author:** ![Aloso](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/aloso/32/5039_2.png) [@Aloso](https://internals.rust-lang.org/u/Aloso)\
**Post date:** [November 13, 2019, 12:32am UTC](https://internals.rust-lang.org/t/pre-rfc-read-only-visibility/11280/1 "2019-11-13T00:32:20Z")

</div>

# Summary

Add read-only visibility to fields, e.g. `pub(&)`, `pub(&crate)`, `pub(crate, &)`. A read-only field can _not_ be assigned to or mutably borrowed.

# Motivation

Visibility modifiers of structs provide **encapsulation** , to hide implementation details and prevent modifications that violate the struct's invariants.

Often it's okay to make fields visible, as long as they aren't modified. To do this, we have to make them private and add getters. However, this has several disadvantages:

- private fields can't be destructured or pattern-matched
- you might need _two_ getters per field: one borrowing the struct and one moving it
- getters can't take ownership of just a part of the struct
- converting a public field into a private field with getter is a lot of work, if the field is used a lot

# Guide-level explanation

A visibility modifier of a field can contain a “`&`” to indicate that the field can be immutably borrowed, but not mutably. In other words, you can get a shared reference to the field, but not an exclusive reference.

## Example:

```rust
pub struct Foo {
    pub(&) inner: Inner,
}

pub struct Inner();

```

This is allowed _anywhere_:

```rust
// assume that foo has the type Foo.

let Foo { inner: _ } = foo; // destructuring
if let Foo { inner: Inner() } = foo {} // pattern matching
let inner = foo.inner; // move

```

This is forbidden in other modules:

```rust
let mut foo = Foo {
    inner: Inner(), // error: Foo::inner is read-only
};

let inner = &mut foo.inner; // error: foo.inner can't be mutably borrowed,
                            // because it's read-only

foo.inner = Inner(); // error: foo.inner can't be assigned,
                            // because it's read-only

```

## Different visibility modifiers

- `pub(&)` - read-only visibility everywhere
- `pub(&in simple_path)` - read-only visibility in `simple_path`.
- `pub(&crate)` - read-only visibility in the same crate
- `pub(&super)` - read-only visibility in the parent module
- `pub(&self)` - read-only visibility in the same module (this has no effect)

Additionally, read-only visibility can be **combined** with normal visibility, for example:

- `pub(crate, &)` - full visibility in the same crate, read-only visibility everywhere
- `pub(super, &crate)` - full visibility in the parent module, read-only visibility in the same crate

The full visibility must appear _before_ the read-only visibility.

## Mutability? Sharability? Writability?

In Rust there's some disagreement what these terms mean. In Rust, shared references are called “immutable” although that's not always the case. However, I'd like to ignore this aspect for a moment.

I use the term “read-only” instead of other choices like “immutable”, “sharable” or “non-exclusive”, because things like immutability or sharability are **inherent properties of reference types**. A read-only field, on the other hand, can only be written to _in certain modules_. It's not about the type system, it's about the module system.

EDIT: See below in _Unresolved Questions_ for more information.

# Reference-level explanation

struct and union fields get a new kind of visibility modifier, called `DataVisibility`.

## Grammar

```python
DataVisibility ::= 'pub'
                 | 'pub' '(' VisibilityScope ')'
                 | 'pub' '(' '&' ')'
                 | 'pub' '(' '&' VisibilityScope ')'
                 | 'pub' '(' VisibilityScope ',' '&' ')'
                 | 'pub' '(' VisibilityScope ',' '&' VisibilityScope ')'

VisibilityScope ::= 'crate' | 'self' | 'super' | 'in' SimplePath

```

## Detailed design

There are now two levels of visibility:

- full visibility (same as before)
- read-only visibility (a subset of full visibility)

A field always has full visibility in the module in which it was declared and its sub-modules. It can be extended by specifying a full visibility, read-only visibility, or both. The full visibility takes precedence: `pub(crate, &crate)` is equivalent to `pub(crate)`.

The read-only visibility should be _larger_ than the full visibility. For instance, `pub(crate, &crate)` or `pub(&self)` doesn't make sense and should issue a warning.

In places where a field has _only_ read-only visibility, the following things are allowed:

- destructuring the field
- pattern matching on the field
- borrowing the field immutably
- taking ownership of the field/moving it

The following things are _not_ allowed:

- passing a value for the field to the struct's initializer _(see unresolved questions below)_
- assigning it a new value
- borrowing the field mutably

# Drawbacks

It makes the module system more complicated.

# Rationale and alternatives

- **Properties** (getters and setters that can be used like a field) have been proposed at least twice. They are common in languages like Kotlin or Typescript. In Rust, implementing properties would be difficult, and their benefits would be very limited.

- **Also have a notion of write-only**. This wouldn't be very useful: It wouldn't allow you to borrow it, and there aren't many use cases anyway

- **Allow writing the read-only visibility before the full visibility** : This might lead to confusion, because `pub(&,crate)` looks very similar to `pub(&crate)`.

- **Make nonsensical visibility a hard error** , e.g. `pub(crate, &crate)` or `pub(&self)`. I think supporting this syntax might be desirable in edge cases (e.g. macros), so a warning should be enough.

- **Different syntax** : Ideas in [this thread](https://internals.rust-lang.org/t/idea-properties/11152/30) include `&pub(crate)`, `pub(crate as &)`, `pub(&, &mut crate)`, `pub(ref crate)`, `pub(get, set crate)`

Of all the possible designs I considered, I believe that my proposal is the simplest, most consistent, and easiest to understand.

# Prior art

Property with a private setter in Kotlin:

```kotlin
var setterVisibility: String = "abc"
    private set // the setter is private and has the default implementation

```

And C#:

```cs
public int MyProperty { get; private set; }

```

Typescript has a `readonly` keyword for fields, but with a different purpose, so you need to implement a getter:

```typescript
private _prop: string;
public get prop() : string {
    return this._prop;
}

```

# Unresolved questions

### Name

I'm not entirely satisfied with the term “read-only”, because it might lead people to believe that fields with read-only visibility are immutable. However, a field with read-only visibility can still be mutated in the same module, and it can be mutated anywhere, if it has interior mutability.

Other ideas:

- sharable
- borrowable
- non-exclusive
- non-unique referencable
- non-writable
- non-settable
- share-only
- fetch-only
- accessible only non-exclusively

I'd like to hear your opinion, and maybe other suggestions! To decide this democratically, I'll create a poll in a few days.

### Initializer

We could allow initializing a struct, even if it contains read-only fields: To prevent this, the struct can be made `#[non_exhaustive]`, which means that the struct can only be initialized in the _same module_. This feature is not stable yet, but it's likely that it will be stabilized soon-ish.

- **PRO** : The rules for read-only visibility become easier: Read-only visibility only prevents _borrowing the field mutably_.

- **CON** : A macro must be added to all structs with read-only visible fields. If you forget this, the struct's invariants can be violated.

- **CON** : We can't write `#[non_exhaustive(super | crate | in path)]`.

---

<div class="post-metadata">

**Author:** ![Aloso](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/aloso/32/5039_2.png) [@Aloso](https://internals.rust-lang.org/u/Aloso)\
**Post date:** [November 13, 2019, 12:33am UTC](https://internals.rust-lang.org/t/pre-rfc-read-only-visibility/11280/2 "2019-11-13T00:33:45Z")

</div>

This is my first RFC, please tell me if there are mistakes or something important is missing, I appreciate any help!

---

<div class="post-metadata">

**Author:** ![RustyYato](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/rustyyato/32/13627_2.png) [@RustyYato](https://internals.rust-lang.org/u/RustyYato)\
**Post date:** [November 13, 2019, 12:42am UTC](https://internals.rust-lang.org/t/pre-rfc-read-only-visibility/11280/3 "2019-11-13T00:42:36Z")

</div>

As I said in the linked thread, I am extremely against using `get/set` terminology because it is actively misleading.

Also note, there is no way to specify read-only in Rust due to the existence of `UnsafeCell` and all things built on top of it (modulo the compiler internal `Freeze` auto trait). I don't think that read-only fields is the correct way to sell this feature. It is better to lean into the notion that `&T` is a _shared_ reference to `T`, so these fields would be _shareable_ fields.

> [@Aloso](#):
>
> **Make nonsensical visibility a hard error** , e.g. `pub(crate, &crate)` or `pub(&self)` . I think supporting this syntax might be desirable in edge cases (e.g. macros), so a warning should be enough.

I think a deny by default warning is good for this.

> [@Aloso](#):
>
> - **Also have a notion of write-only**. This wouldn't be very useful: It wouldn't allow you to borrow it, and there aren't many use cases anyway

Similarly to how Rust doesn't have a notion of read only, it doesn't have a notion of write only, so it would be impossible to enforce this other than just allowing assignment. Not allowing references to these fields is not significantly better than just having a setter for these fields.

* * *

link to previous thread where I spoke about this,

> [@Idea: Properties](https://internals.rust-lang.org/t/idea-properties/11152/32):
>
> But this is wrong. You can mutate through a shated reference, for example, atomics. That us the entire point of staying away from the mutability view. Using get/set terminology is actively misleading because it gives the impression that you can't mutate fields that you only have get access to. I think that this will be a source of bugs that will only be explainable by using the exclusivity view, so we should just stay aligned to that from the start. This is why I used the ref keyword in my sy…

---

<div class="post-metadata">

**Author:** ![Aloso](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/aloso/32/5039_2.png) [@Aloso](https://internals.rust-lang.org/u/Aloso)\
**Post date:** [November 13, 2019, 12:48am UTC](https://internals.rust-lang.org/t/pre-rfc-read-only-visibility/11280/4 "2019-11-13T00:48:00Z")

</div>

As I explained in the RFC: A read-only field is _not_ immutable, it allows interior mutability (just like “immutable borrows”).

This RFC is not about forbidding mutability. I knew that some people would misunderstand it, that's why I wrote a section about it.

If you know a better name for this feature, I'd like to hear it!

---

<div class="post-metadata">

**Author:** ![Nokel81](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/nokel81/32/3966_2.png) [@Nokel81](https://internals.rust-lang.org/u/Nokel81)\
**Post date:** [November 13, 2019, 12:52am UTC](https://internals.rust-lang.org/t/pre-rfc-read-only-visibility/11280/5 "2019-11-13T00:52:41Z")

</div>

If it is not immutable then why not call it "non-unique referencable"

---

<div class="post-metadata">

**Author:** ![Aloso](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/aloso/32/5039_2.png) [@Aloso](https://internals.rust-lang.org/u/Aloso)\
**Post date:** [November 13, 2019, 12:53am UTC](https://internals.rust-lang.org/t/pre-rfc-read-only-visibility/11280/6 "2019-11-13T00:53:35Z")

</div>

> I use the term “read-only” instead of other choices like “immutable”, “sharable” or “non-exclusive”, because things like immutability or sharability are **inherent properties of a type**. A read-only field, on the other hand, can only be written to _in certain modules_ . It's not about the type system, it's about the module system, this is an important distinction.

---

<div class="post-metadata">

**Author:** ![RustyYato](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/rustyyato/32/13627_2.png) [@RustyYato](https://internals.rust-lang.org/u/RustyYato)\
**Post date:** [November 13, 2019, 1:02am UTC](https://internals.rust-lang.org/t/pre-rfc-read-only-visibility/11280/7 "2019-11-13T01:02:02Z")

</div>

Yes, I saw that section, I just wanted to reiterate my thoughts because of just naming this read only fields will cause confusion. I guarantee it. There will be a number of people who make a fields with interior mutability "read only", and then see that it does in fact change values. I will see these people on URLO, in the same way that people are confused about what `&T` actually means. I would like to pre-emptively stop this.

Rust tries to push as much into the type-system as it feasibly can, which is why we have the distinction between `&T` and `&mut T` be a type distinction. So by saying

> [@Aloso](#):
>
> can only be written to _in certain modules_ . It's not about the type system, it's about the module system, this is an important distinction.

it is actively discrediting the type-system. The only way to access these fields are through references, and since we are talking about access modifiers (visibility), I think we need to lean into the type system is inherently involved.

> [@Aloso](#):
>
> Other languages, such as Typescript, also have readonly fields with interior mutability.

Other languages don't have a distinction about _how_ data can be accessed with the same granularity that Rust does. They only have the broad strokes of the privacy system. With Rust, we have two difference ways a field could be accessed in addition to the privacy system, by shared reference (`&T`) or by exclusive reference (`&mut T`), so we should use that nomenclature to specify our fields visibility.

> [@Aloso](#):
>
> If you know a better name for this feature, I'd like to hear it!

shareable

> [@Aloso](#):
>
> things like immutability or sharability are **inherent properties of a type**

I disagree, I don't see sharability as a property of the type. That is why we have _shared_ references, references that can be freely shared that refer to any type.

---

<div class="post-metadata">

**Author:** ![atagunov](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/atagunov/32/5877_2.png) [@atagunov](https://internals.rust-lang.org/u/atagunov)\
**Post date:** [November 13, 2019, 1:02am UTC](https://internals.rust-lang.org/t/pre-rfc-read-only-visibility/11280/8 "2019-11-13T01:02:10Z")

</div>

`&-visibility` for lack of a better term? 🙂

---

<div class="post-metadata">

**Author:** ![RustyYato](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/rustyyato/32/13627_2.png) [@RustyYato](https://internals.rust-lang.org/u/RustyYato)\
**Post date:** [November 13, 2019, 1:03am UTC](https://internals.rust-lang.org/t/pre-rfc-read-only-visibility/11280/9 "2019-11-13T01:03:25Z")

</div>

That only works in text, we want something that we can also talk about 🙂

---

<div class="post-metadata">

**Author:** ![comex](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/comex/32/2587_2.png) [@comex](https://internals.rust-lang.org/u/comex)\
**Post date:** [November 13, 2019, 1:29am UTC](https://internals.rust-lang.org/t/pre-rfc-read-only-visibility/11280/10 "2019-11-13T01:29:20Z")

</div>

It seems mildly surprising that `pub(&)` would allow moving. I’m not sure what a good alternative syntax might be, though.

Generally speaking, I sympathize with @RustyYato‘s concern about interior mutability being confusing, but we’ve made that bed and now we have to lie in it. The `mut` in `&mut` is short for “mutable”, and that’s not going to change – so we can’t really stop calling them “mutable references”, and so we’re stuck having to teach users that “you can sometimes mutate things via non-mutable references”.

For this reason, I’d be okay with syntax alternatives such as `pub(get)` or `pub(ro)`, even though they reinforce the misleading terminology.

Edit: FWIW, “read-only” and “immutable” seem synonymous to me.

---

<div class="post-metadata">

**Author:** ![RustyYato](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/rustyyato/32/13627_2.png) [@RustyYato](https://internals.rust-lang.org/u/RustyYato)\
**Post date:** [November 13, 2019, 1:50am UTC](https://internals.rust-lang.org/t/pre-rfc-read-only-visibility/11280/11 "2019-11-13T01:50:51Z")

</div>

> [@comex](#):
>
> so we can’t really stop calling them “mutable references”

We can, I do and it does clear up confusion. Just because some short sighted decisions were made in the past, doesn't mean we have to keep them.

I call them exclusive references, and then parenthetically say (`&mut T`) in case someone hasn't come across the terminology before.

> [@comex](#):
>
> “you can sometimes mutate things via non-mutable references”.

Instead say, you can sometimes mutate things through shared references. Then you can link to the amazing read by @mbrubeck: [Rust: A unique perspective](https://limpet.net/mbrubeck/2019/02/07/rust-a-unique-perspective.html)

---

<div class="post-metadata">

**Author:** ![Aloso](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/aloso/32/5039_2.png) [@Aloso](https://internals.rust-lang.org/u/Aloso)\
**Post date:** [November 13, 2019, 10:41am UTC](https://internals.rust-lang.org/t/pre-rfc-read-only-visibility/11280/12 "2019-11-13T10:41:37Z")

</div>

> [@RustyYato](#):
>
> Rust tries to push as much into the type-system as it feasibly can

Yes, but visibility/privacy is still a separate concept. As a rule of thumb: The type system provides safety. The module system provides encapsulation.

> [@RustyYato](#):
>
> The only way to access these fields are through references

That is not true, and part of the reason why I went for the name “read-only”. A field with read-only visibility can be **moved** , and it can't be used in the initializer. This is unrelated to shared borrows, so terms like “sharable” or “not uniquely borrowable” will be confusing.

In my first draft I called it “share-only”, but changed it because of this.

> [@RustyYato](#):
>
> I disagree, I don't see sharability as a property of the type. That is why we have _shared_ references, references that can be freely shared that refer to any type.

What I meant is that immutability or sharability are inherent properties of _reference types_ such as `&[String]`. These properties don't change when passing a reference to a different module, but the visibility _can_ change.

---

<div class="post-metadata">

**Author:** ![Aloso](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/aloso/32/5039_2.png) [@Aloso](https://internals.rust-lang.org/u/Aloso)\
**Post date:** [November 13, 2019, 10:49am UTC](https://internals.rust-lang.org/t/pre-rfc-read-only-visibility/11280/13 "2019-11-13T10:49:39Z")

</div>

> [@RustyYato](#):
>
> As I said in the linked thread, I am extremely against using `get/set` terminology because it is actively misleading.

This is your explanation in the other thread:

> [@Idea: Properties](https://internals.rust-lang.org/t/idea-properties/11152/32):
>
> Using `get` / `set` terminology is actively misleading because it gives the impression that you can't mutate fields that you only have `get` access to.

I think this is not true. Having a getter doesn't mean that the returned value is immutable. It's not true in Rust, and it's definitely not true in other languages.

I am thinking about renaming the feature to `non-settable fields`.

---

<div class="post-metadata">

**Author:** ![kentnl](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/kentnl/32/6165_2.png) [@kentnl](https://internals.rust-lang.org/u/kentnl)\
**Post date:** [November 13, 2019, 12:53pm UTC](https://internals.rust-lang.org/t/pre-rfc-read-only-visibility/11280/14 "2019-11-13T12:53:32Z")

</div>

> [@Aloso](#):
>
> A field with read-only visibility can be **moved** , and it can't be used in the initializer.

That seems like a bit of an anti-feature. Surely we can have a read-only visibility rule that demands the thing its applied to can be implicitly copied/cloned instead of moved, and surely we can provide a mechanism to forbid using this feature for items which would expose interior mutability.

Side note: `pub` syntax is available on functions too, what would `pub(&) fn foo () { }` do?

---

<div class="post-metadata">

**Author:** ![Aloso](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/aloso/32/5039_2.png) [@Aloso](https://internals.rust-lang.org/u/Aloso)\
**Post date:** [November 13, 2019, 1:06pm UTC](https://internals.rust-lang.org/t/pre-rfc-read-only-visibility/11280/15 "2019-11-13T13:06:37Z")

</div>

I don't think that this kind of restriction would be useful. Fields with read-only visibility should work exactly the same as normal fields.

Note that getters (e.g. `pub fn get_foo(&self) -> &Foo`) can expose interior mutability as well, so this is not new at all.

Read-only visibility only applies to **struct and union fields** , not to functions/types/traits/etc.

---

<div class="post-metadata">

**Author:** ![kentnl](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/kentnl/32/6165_2.png) [@kentnl](https://internals.rust-lang.org/u/kentnl)\
**Post date:** [November 13, 2019, 1:15pm UTC](https://internals.rust-lang.org/t/pre-rfc-read-only-visibility/11280/16 "2019-11-13T13:15:48Z")

</div>

> [@Aloso](#):
>
> Note that getters (e.g. `pub fn get_foo(&self) -> &Foo` ) can expose interior mutability as well, so this is not new at all.

But you're much less likely to have some field _moved_ by a consumer without explicitly making that happen in the getter.

The idea that somebody could move out some field of my struct worries me, but I might be misunderstanding things.

I would only be wanting people to copy or borrow, not like, take ownership.

---

<div class="post-metadata">

**Author:** ![Aloso](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/aloso/32/5039_2.png) [@Aloso](https://internals.rust-lang.org/u/Aloso)\
**Post date:** [November 13, 2019, 1:19pm UTC](https://internals.rust-lang.org/t/pre-rfc-read-only-visibility/11280/17 "2019-11-13T13:19:06Z")

</div>

A field can only be moved out of a struct, if the struct is _owned_, not borrowed, so this is no problem. If the struct is borrowed, you still have to borrow, copy or clone the field.

---

<div class="post-metadata">

**Author:** ![gbutler](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/gbutler/32/3670_2.png) [@gbutler](https://internals.rust-lang.org/u/gbutler)\
**Post date:** [November 13, 2019, 4:11pm UTC](https://internals.rust-lang.org/t/pre-rfc-read-only-visibility/11280/18 "2019-11-13T16:11:50Z")

</div>

Yes, but, how would invariants be properly upheld within the defining module if the fields could be "moved" out of the struct? Are you saying that all "unsafe" code in the module defining the struct could not rely upon the field value being invariantly populated with a legitimate value for the field type? That sounds completely unsound to me. Can someone with more expertise than myself weigh in on this?

EDIT: Thinking and reflecting on this more, I realized that as soon as you move any field out of a struct, then that structs is no longer allowed to have methods called on it that take it as an argument, and so, this is, after-all sound. No?

---

<div class="post-metadata">

**Author:** ![Aloso](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/aloso/32/5039_2.png) [@Aloso](https://internals.rust-lang.org/u/Aloso)\
**Post date:** [November 13, 2019, 4:20pm UTC](https://internals.rust-lang.org/t/pre-rfc-read-only-visibility/11280/19 "2019-11-13T16:20:51Z")

</div>

Yes, I believe so. A soon as at least one field is moved out of a struct, the struct can't be used anymore. It's the same with destructuring.

---

<div class="post-metadata">

**Author:** ![gbutler](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/gbutler/32/3670_2.png) [@gbutler](https://internals.rust-lang.org/u/gbutler)\
**Post date:** [November 13, 2019, 4:21pm UTC](https://internals.rust-lang.org/t/pre-rfc-read-only-visibility/11280/20 "2019-11-13T16:21:43Z")

</div>

> [@Aloso](#):
>
> Yes, I believe so.

I agree (as my edited comment above reflects after I though about it more).

[Next page](https://internals.rust-lang.org/t/pre-rfc-read-only-visibility/11280.md?page=2)
