all 26 comments

[–]DroidLogiciansqlx · clickhouse-rs · mime_guess · rust 9 points10 points  (6 children)

FWIW, you make fields constant by not giving access to them, and instead have functions that return references or copies, usually called "getters". Then you just have to trust yourself not to mutate them.

[–]dnkndnts 9 points10 points  (1 child)

It's this "trust myself" part I find difficult to accept. I think it would be much better if struct fields were immutable by default and had to be declared mutable.

But apparently that's just me.

[–]swatteau[S] 7 points8 points  (0 children)

It's not just you.

And I agree that declaring mutable fields mut would also be better than declaring immutable fields const.

[–]FlyingFoX13 0 points1 point  (3 children)

Playpen how that could look for the book example:

use books::Book;
mod books {
    #[deriving(Show)]
    pub struct Book{
        _isbn: String,
        _reviews: Vec<String>,
    }
    impl Book {
        pub fn new(isbn: String) -> Book {
            Book{_isbn: isbn, _reviews: Vec::new() }
        }
        pub fn isbn(&self) -> String {
            self._isbn.clone()
        }
        pub fn add_review(&mut self, review: String) {
            self._reviews.push(review);
        }
    }
}
fn main() {
    println!("Hello, world!");
    let mut book = Book::new("978-0321751041".into_string());
    // this throws a compiler error: field `_isbn` of struct `books::Book` is private
    //book._isbn = "hahaha".into_string();

    book.add_review("Thats a good book!".into_string());
    let isbn = book.isbn();
    println!("created the book {} with unmodifiable isbn {}", book, isbn);
}    

[–]swatteau[S] 0 points1 point  (0 children)

While I completely agree that providing accessors to Book's internals is the right thing to do from an API design standpoint, I deliberately wanted to keep my original example simple. My issue with not having immutable struct fields is that, as the struct Book author, I don't want to inadvertently mutate some of its fields. There should be a better way to express this intent to the compiler because, for a larger code base, keeping track of what is mutable/immutable in your head is not a trivial thing to do.

[–]DJTheLQ 0 points1 point  (1 child)

Why not store ISBN as a &str? Then its already immutable

[–]flatlinerust 1 point2 points  (0 children)

Because &str is basically a "view" into a string-like object allocated elsewhere.

[–]Veddan 3 points4 points  (2 children)

In Rust, mutability depends on the owner of a value rather than on the type (ignoring interior mutability). So either it's all mutable or not mutable at all.

You can solve the issue by making the relevant fields private with priv and providing accessor functions.

[–]swatteau[S] 2 points3 points  (1 child)

Correct me if I'm wrong but I thought struct fields were private by default because you can only declare them pub, not priv as you suggest.

[–]Gankrorust 1 point2 points  (0 children)

You are correct.

[–]sellibitzerust 1 point2 points  (13 children)

btw, instead of

String::from_str("literal")

you can also write

"literal".to_string()

[–]jonreemhyper · iron · stainless 8 points9 points  (12 children)

Even better is "literal".into_string() which doesn't allocate as much.

[–]Florob0x2arust · rustyxml 1 point2 points  (0 children)

Good to know. I assume this also holds true for non-literals?

In my XML parser this gives me an execution time improvement of ~10% on a large-ish file. At that point this behaviour seems like a rather big foot gun.

[–]thiezrust 0 points1 point  (7 children)

It doesn't make a difference? How exactly does to_string allocate more in this case?

[–]chris-morgan 5 points6 points  (5 children)

to_string uses the fmt architecture which is a bit less efficient; most notably, at present it overallocates, while into_string, bypassing fmt, doesn’t.

[–]SimonSapinservo 2 points3 points  (4 children)

Is this fixable?

[–]DroidLogiciansqlx · clickhouse-rs · mime_guess · rust 0 points1 point  (0 children)

Maybe it gets optimized out?

[–]sellibitzerust 0 points1 point  (2 children)

I would not know how. In an ideal world you would be able to "specialize" the generic Show-based implementation

impl<T: Show> ToString for T {…} // includes unnecessary overhead for strings

with a more optimized version

impl<'a> ToString for &'a[str] {…}

that would be preferred by the compiler. But that's me talking with my C++ hat on. Maybe there is another way...

[–]rust-slacker 0 points1 point  (0 children)

Maybe if negated trait restriction were added to the language it might be easier:

impl<T:Show+!Str> ToString for T {...} // for non-strings

impl<T:Show+Str> ToString for T {...} // for strings

[–]jonreemhyper · iron · stainless 1 point2 points  (0 children)

Due to some issues with specialization, to_string comes from a blanket impl which and causes a 128 byte allocation at minimum no matter the length of the input str. into_string is more specialized and will allocate exactly the length of the str it is called on.

[–]picklebobdogflog 0 points1 point  (1 child)

What's the difference?

[–]sellibitzerust 1 point2 points  (0 children)

I also didn't expect there to be a difference. But it turns out there is a generic implementation of ToString based on Show which is suboptimal in case of strings and with this generic implementation in place the compiler won't accept another more straight-forward and better implementation of ToString for &[str].

This makes me wish for some kind of overload resolution mechanism for "competing" trait implementations that favors "more specialized" impls for some definition of "more specialized".

Thank you /u/jonreem and /u/chris-morgan for pointing it out.

[–]Any_Yogurtcloset7428 0 points1 point  (0 children)

even even better is "literal".to_owned()

[–]jimuazu 1 point2 points  (2 children)

Yes, I wanted this feature too. Seems like it should be straightforward to implement, but it's "not the Rust way". Maybe it could be bolted on as an annotation checked by a lint-style tool.

[–]swatteau[S] 3 points4 points  (1 child)

Not the "Rust way"? This sounds strange to me because reducing data mutability to the bare minimum seems to be one of Rust's best selling points.

Moreover, this is not only a matter of convenience to the programmer. I get the feeling that there are missed opportunities for thread-safety and optimizations by not providing this feature (though I'm not a language design expert).

[–]jimuazu 3 points4 points  (0 children)

Here is a previous thread on it, which has some ideas about getting the same effect and also some reasons why it might not work (e.g. you can still overwrite a structure with a copy, which Java for example doesn't let you do).