A type annotation for a mutable vector of mutable strings


#1

I’m just playing around with Rust (just went through the first chapter in the Rust book). I’m trying to define a mutable vector of mutable strings, with an explicit type annotation, but I can’t figure out how.
I feel I must be missing something really basic, but I can’t find any clear explanation anywhere - all questions and references about type annotations seem to be about lifetime annotations, which I haven’t delved into yet.

I know that type annotations are usually optional, but I’m just trying out Rust and would like to learn to define them

I’ve tried the following, they both fail to compile:

let v : mut Vec<mut String> = Vec::new();
let v : Vec<mut String> mut = Vec::new();

#2

Using only Vec does not specify a type. The Vec container needs to know what it stores, so you tell it via a type parameter. It looks like this:

fn main() {
    println!("Hello, world!");

    // This is the line you should care about the most.
    let mut my_values: Vec<i32> = vec![1,4,2,7,4,7,9];
    
    my_values[3] = 99;
    
    println!("The vector is now: {:?}.", my_values);
}

Simmilar, a Vec<String> will store many String values (this is not the only “string” type). But beware, strings are tricky and rust doesn’t hide that from you. If you don’t want to deal with that complexity starting out, than https://crates.io/crates/easy_strings can hide it.


#3

Thanks for your answer. I know about type parameters. I’m very sorry, but the formatting of my samples wasn’t preserved after I posted, I forgot to format it as code. I’ve corrected it now. I hope my question is clearer.


#4

Types don’t have mutability (with exception for reference types like &mut i32), variables have.

// If you want to define a type (but you don't need in this case),
// use `let mut v: Vec<String>`
let mut v = Vec::new();
v.push(String::from("Hello"));
v[0].push_str(", world!");
println!("{}", v[0]);

In fact, you can easily change an immutable variable into mutable one.

let v = Vec::new();
let mut v = v;

#5

Thanks, it seems I was confused by the const keyword from C++, where it’s essentially always a part of the type.

But it’s still confusing. Comparing this to C++, it looks like this “v” has some “magic type” - it’s a variable binding, but it has something like a “flag” or something on it (from the view of the type system) which says “mutable” or “immutable”, but this flag isn’t part of the type I can specify, that is, it’s not accessible to me?

Or is it something like: Types are not immutable/mutable as of themselves, but variable bindings to them are?
If so, I’m still slightly confused when you throw references into the mix: is being a reference a part of the binding, or part of the type? I’d guess it’s part of the binding, but I’m not sure.

And in your last example, this is also confusing. From what I understood, I’m not so sure you change an immutable variable into a mutable one. I think that a new variable binding is introduced which shadows the previous binding since they have the same name, so they’re conceptually different "v"s. Of course for all practical purposes we can treat this like the “v” binding changed its binding type.


#6

In a way, it’s not that different from C++. This is an invalid C++ code.

std::vector<const int> hi = {1, 2};

However, this is a valid code.

const int a = 3;
const int& b = a;

Just like in C++, variables, references and pointers in Rust store their mutability. But that’s about it (as far Rust is concerned, C++ allows for say const member variables, but generally the rule is very similar).

The last example however is different to how C++ works. In C++, const variable may as well be stored in immutable memory. However, in Rust, this is only the case when the variable is not modified (which may happen if it was moved into mutable variable, like in example I provided) - a move will happen, not copy like in C++ (even when explicitly using std::move).


#7

These are good questions. mut, as you guessed serves a double function: it can modify a binding to be mutable, and it can indicate, in &mut, a mutable reference. In the latter, it is very much part of the type.

The connection is that &mut references can only be created through mut bindings (and of course, when borrow rules are satisfied). And mutating methods have a &mut receiver, so calling them requires creating a &mut reference.

To go back to the original question, Vec<String> is an owned vector of strings. Mutating it requires at some point creating a &mut Vec<String>, so whether it can be done depends on the mutability of the binding through which the vector is accessed.


#8

Thanks a lot! That got me thinking for a few hours and I experimented a bit.
I think I understand better now, but some parts still seem tricky.

So, as I understand it, mut can appear in 3 places which have separate meanings:

  1. As an “extra” type annotation &mut T
    e.g. let j : &mut i8 = ...
    It can never appear by itself on a type T, without the ‘&’ as opposed to the “const” C++ keyword, which can appear on a non-reference type. In a sense I think &mut is a modifier all by itself. In other words, a type can have & or &mut appear in front of it, but not mut .

  2. By itself: as a modifier on a variable binding, in a let statement or in a function parameter.
    e.g. let mut j : i8 = ...
    This still looks to me like a hidden “flag” set on the binding, saying whether this binding, this name, can be mutated or not. I call it “hidden” since it looks to me like an extra type on top of the type system, I mean, given that the type of j above looks like it’s i8, right? And nothing is said about the mutability in the type itself, only on the binding.

  3. As an operator on a binding used in an expression, but only together with &. I think this is what borrowing is, but I’m not sure.
    E.g. let … = &mut j;
    and & can also be used on its own
    E.g. let … = &j;
    As I see it, this is different than (1) and (2) above since it isn’t a type (obviously) and it doesn’t declare the “mutability” of a binding. It kinda looks like a reference followed by a cast - I’m taking a reference of j and then casting the reference to be mutable. But I checked and casting (the “as” keyword) doesn’t allow casting into mut, so it looks like some special casting that can only be used on a reference to a binding.

As for the Vec<String> - you answered about the mutable Vec, and xfix above answered me about the mutable owned string by pointing out that this mutability of the owned strings doesn’t make sense even in C++, So the owned string are essentially as mutable as their container is, and since the owner’s mutability can be changed, so can the mutability of the owned strings.


#9

Thanks, your explanation and birkenfeld helped me a lot.
The part that does look to me like it’s a bit different from C++, and what initially confused me, was that :

let mut i : i32 = 8; // "mutating" looks like it's not part of the type of i
looks different than:

const int i = 8; // "immutating" (is that a word?) *is* part of the type of i

I’m not sure if that’s just pure syntax differences or something deeper. I thought it was something deeper and wrote about it above in my response to birkenfeld, but now I’m not so sure…


#10

Rust defaults to immutable, while C++ defaults to mutable for bindings. Both languages share variable mutability for inner attributes (although C++ allows marking member variables as const) with exception for pointers/references which store their own mutability as part of type (&T and &mut T are distinct types, so is const T* and T*).


#11

You’re right, I didn’t mention the usage you’re listing as #3.

  1. correct, it’s either &, &mut or nothing.
  2. it’s a flag, but not on the type and it’s not hidden. It’s plainly there in let mut. Since it’s not part of the type you can freely rebind the value with a different mutability:
let a = Vec::new();
let mut b = a;
b.push(1);

.3. here, &mut is not part of type name, it is an operator that creates values of a &mut T type - just like & creates a value of a pointer type in C.