Pre-RFC: Making struct attributes unmutable

Summary

Add a way to have immutable struct attributes in Rust which can't be changed by anyone after initialization.

Motivation

struct Foo {
    size: usize,
    max_capacity: usize,
}

When we create structs like this, the attributes size and max_capacity are mutable using Foo's methods(using impl). If we make them public, they can be mutated by anyone.

However there are use-cases in which these attributes are not supposed to be changed after initializing the struct not by outside world nor using own impl methods. Having immutable struct attributes could possibly help reduce bugs and improve readability of code and making a struct and its usage more clear.

Guide

We have mut keyword to make variables mutable. For example the first one here is mutable and the latter immutable:

let mut a = 10;
let b = 20;

For structs we can have mutable attributes(by default) and to make them immutable, use a new immut keyword:

struct Foo {
    size: usize,
    max_capacity: immut usize,
}

Or vice versa. e.g. Using the existing mut keyword, make attributes mutable using the keyword and immutable by default.

I am not sure if the former is better or latter. However I guess because users usually want to change struct attributes after creation, the former solution is better.

Similar Pre-RFCs

I am a user of Rust and this is my first. Any help appreciated.

Do you have any data to support this? And in particular, do you have any data that the current features to keep bindings immutable are insufficient?

1 Like

No, I don't have any data to support it. However I'm making this logical argument that when I make an attribute immutable, the future readers of my code without any need for comments will understand that this attribute won't change in the methods of the struct nor can be changed outside world if the attribute is public. And also I have this message for them that it is probably a bad decision to change immutable attribute to mutable in the future.

To quote something I just posted yesterday

In many other programming languages, defining something like a “struct” or maybe “class” with fields that are not protected to be read-only by hiding them behind getters, will immediately result in multiple copies of (references to) your object sharing mutable state. Rust works differently. You will never accidentally run into shared mutable state (in fact, it can be quite tedious to get any shared mutable state in the first place in the cases where you might actually need it), so the main motivation for disallowing mutation is gone.

There are remaining cases where preventing mutation of fields is sensible though. This mainly happens if the struct upholds some kind of invariant/property of the data contained within it which is checked on construction and relied upon in its methods/API.

With this in mind, I am not aware of any situation where preventing mutability entirely, including in Foo’s own methods, is a very sensible thing to do. It’s almost not even a sensible restriction in the first place. An inherent method (or any other function defined where the fields are all visible) could always just destructure the datatype into its constituent parts, mutate the parts, and re-assemble them into a new instance of the struct.[1]

The previous Pre-RFC you quoted was notably not concerned with limiting mutable access completely but only allowing an additional form of encapsulation so that … ehm … well this seems to be not really explained in that RFC, but naturally one should compare such a feature to the possibility of making the fields private and offering a set of (&self) -> &TypeOfField methods … and a dedicated feature for read-only public access to fields could allow: reducing boilerplate; and nicer syntax at the use-site; and maybe more fine-grained analysis by the borrow-checker for borrowing individual fields.


  1. It’s only “almost” not sensible since there are some difficulties to this approach when the struct implements Drop. ↩︎

1 Like

Practical demonstration

struct Foo {
    size: usize,
    max_capacity: usize,
}

fn increase_size(r: &mut Foo) {
    let new_foo = Foo {
        size: r.size + 1,
        max_capacity: r.max_capacity,
    };
    *r = new_foo;
}

// equivalent to, after optimization:
pub fn increase_size2(r: &mut Foo) {
    r.size += 1;
}

(compiler explorer)

1 Like

Are you aware of the restrictions RFC?

Also note that in your example, Foo::size is usually called a "field", whereas "attributes" are things like #[derive(Clone)] -- you might want to fix up your inital post to be less confusing.

6 Likes

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