8
votes

I'm looking at returning a string from a function in Rust, but the only option I see right now is to return String, which can be modified. While it isn't incorrect, I quite like the idea of returning some strings like error descriptions as immutable.

So is there any way for functions currently returning for example Result<Something, String> to actually return the error as immutable? Is it possible to enforce it in the type itself while still returning something Str-compatible? Or should I just stop worrying and always give back Strings? (like most of the std::io functions do)

1
You should critically examine why you feel uneasy about returning a mutable type. In many OOP languages, one avoids returning references to mutable types because that creates aliasing of mutable objects and hence any caller can easily cause strange action-at-a-distance. But this is a non-issue in Rust since if you return a String you return ownership of it and it's statically guaranteed that there are no (other) references to it. - user395760
@delnan I like immutable strings in this situation because you cannot accidentally modify them yourself. Let's say someone that we're still talking about error handling and in the error path I do some_processing(message); log(message);. In python, java, haskell, etc. I'm sure the message has not been modified by some_processing. I can't say the same about rust. Even if local code has ownership, the first function can still mess it up. - viraptor
@viraptor, you can, in fact, say the same about Rust. If message is a String, your code just won't compile because message will be moved into the function. If it is a slice, it is immutable, so you can't do anything dangerous with it. The only way for some_processing() to modify its argument is to pass it via a mutable reference: some_processing(&mut message), and here you can easily see that some_processing() modifies its argument. - Vladimir Matveev
@VladimirMatveev awesome, I didn't realise that! - viraptor

1 Answers

15
votes

Rust employs a concept of inherited mutability - that is, whether the given piece of data is mutable or not is determined by the owner of this piece. Hence if your piece of data is a regular owned structure, like String, you can't influence its mutability after you return it.

There is a kind of workaround, however. As Rust is value-based language, an instance of a structure with a single field of some type has exactly the same representation as just a value of that type, so you can create a wrapper for your type which does not expose its field directly but provides a getter which returns an immutable reference to that field:

struct ImmutableString(String);

impl ImmutableString {
    #[inline]
    fn get_ref(&self) -> &String { &self.0 }

    // or, even better:

    #[inline]
    fn as_slice(&self) -> &str { self.0.as_slice() }
}

Due to compiler optimizations this is a zero-cost wrapper and both methods are inlined away, but because these methods are the only way to interact with the structure internals, and they only returns immutable references, the value of such structure will be effectively immutable.

However, I'd argue that you don't really need all of this. Rust concepts of ownership and borrowing eliminate all of the problems you usually have with mutable data in languages like C or C++ or Java, so just be natural - if you're returning a String, you're giving up all your control over it, so let the caller decide what they want to do with it, there's no need for any restrictions.