Can't borrow mutable more than once

What is the compiler moaning about now? :rage: I tried to create a simple version of what is happening using integers in hopes someone could tell me a workaround. In my actual program when I'm looping I don't need to pass the thing it is moaning about, but of course rust doesn't support variable arguments. Because why would anyone ever need a variable number of arguments, or use a vector more than once?

The quick way to fix this is to just copy the function, but this seems like a stupid thing to do, surely there is a better way?

fn main() 
{
    let mut stuff_to_use = vec![];
    
    fn_stu(&mut stuff_to_use, 1);
    fn_stu(&mut stuff_to_use, 2);
    fn_stu(&mut stuff_to_use, 1000);

    for problem in stuff_to_use.iter_mut() 
    {
        if problem < &mut 100
        {
            fn_stu(&mut stuff_to_use, 2000);
        }
    }
}

fn fn_stu (stuff: &mut Vec<i32>, x: i32 )
{
    stuff.push(x);
}

Rust Playground

What I tend to do is to keep a list of changes to be done in the target vec. On the first iteration (reading), I generate the changes. In the second iteration I write the changes. Something like this:


fn main() 
{
    let mut stuff_to_use = vec![];
    
    fn_stu(&mut stuff_to_use, 1);
    fn_stu(&mut stuff_to_use, 2);
    fn_stu(&mut stuff_to_use, 1000);

    let mut items_to_add = vec![];
    for problem in stuff_to_use.iter_mut() 
    {
        if problem < &mut 100
        {
            
            fn_stu(&mut items_to_add, 2000);
        }
    }
    
    stuff_to_use.extend_from_slice(items_to_add.as_ref());
    println!("{:?}", stuff_to_use);
}

fn fn_stu (stuff: &mut Vec<i32>, x: i32 )
{
    stuff.push(x);
}

You're trying to modify a vector while iterating over it, which is almost never a good idea. In C++ this would result in UB and you would get weird behaviour at runtime. In java you would get a ConcurrentModificationException. Rust instead catches it at compile time. Where is the problem with this?

12 Likes

To make the issue clearer, note that iter_mut returns a struct with pointers into the vector. When you call push on the vector, this can cause it to reallocate, moving the allocation to somewhere else. When this happens, the pointers in the struct returned by iter_mut would point into the old deallocated memory, and continuing to use it would be pretty bad.

This is what the compiler is preventing you from doing.

14 Likes

Ok. I will try just creation of a dummy vector to pass the as a workaround for not having a variable number of arguments. Seems the most straightforward way around this limitation.

It's also possible to iterate over the vector indices, like you would in C. From this code, there's two possible translations, depending on whether the loop should process the new items or not:

/// Processes the newly-created items
fn example1() 
{
    let mut stuff_to_use = vec![];
    
    fn_stu(&mut stuff_to_use, 1);
    fn_stu(&mut stuff_to_use, 2);
    fn_stu(&mut stuff_to_use, 1000);

    for idx in 0.. {
        if idx >= stuff_to_use.len() { break; }
        if stuff_to_use[idx] < 100
        {
            fn_stu(&mut stuff_to_use, 2000);
        }
    }
    
    dbg!(stuff_to_use);
}

/// Processes only the original items
fn example2()
{
    let mut stuff_to_use = vec![];
    
    fn_stu(&mut stuff_to_use, 1);
    fn_stu(&mut stuff_to_use, 2);
    fn_stu(&mut stuff_to_use, 1000);

    for idx in 0..stuff_to_use.len() {
        if stuff_to_use[idx] < 100
        {
            fn_stu(&mut stuff_to_use, 2000);
        }
    }
    
    dbg!(stuff_to_use);
}

Playground

I probably shouldn't ask questions after spending 2-3 hours being frustrated by the compiler and wait until I'm not so annoyed. I understand the reasoning behind the double borrow restriction (I have ran into this before) and because the philosophy of Rust to never trust the programmer knows what they are doing this makes sense.

But the lack of variable arguments? What is the reasoning there? Why do I have to create a dummy vector to pass rather than being able to pass a variable number of arguments? Is this still the "Never trust that a programmer might know what they are doing." thing?

What does this have to do with the code you've posted here? I don't see anything that looks like a varargs implementation.

Can you describe the problem you want to use a variadic function for instead of the workaround you've come up with?

1 Like

There are cases where the compiler's normal rules prevent you from writing code that's perfectly sound, and you need a way to tell it "don't worry, I know what I'm doing"—that's what unsafe is for—but this is not one of those cases. Your original code really is buggy for the reason @alice described, and it really would cause problems at run time in another language.

1 Like

The code I posted here, was to show the problem with the double borrowing, which was the problem I generated when I was trying to figure out how to get around the inability to do variant arguments. I tried to make a simplistic version which showed the compiler problem. This problem was as a consequence of me trying to work around the arguments' limitation. It wasn't my original problem.

But actually someone did give me the clue in the thread, which allowed me to do what I needed by just creating an empty vector to pass when I want to call the function without that argument.

The usual solution to this would be to accept an Option<Vec<_>> for that argument instead. If it’s not needed for a particular call, you can give it None.

Ahhhh ! That is probably what I want. I will try that instead of the empty vector.

Thanks.

It's a wanted feature ("variadic generics"), but also one that would probably take a long time to develop and definitely one that is difficult to design well. Past efforts:

1 Like

It's surprisingly hard to design an argument-passing convention that can deal with variable numbers of arguments while also compiling down to a reasonable native ABI. It can be done, but the obvious implementations end up looking, at the machine-code level, a lot like "allocate a vector and then pass a pointer on the stack" or like "allocate a vector on the stack and then pass a pointer in a register."

It's also hard to design a type system that can accommodate the type Fn(U...) -> V, and to define the affordances people expect around variadic arguments in modern languages (being able to unpack a list or a tuple into arguments, being able to iterate over arguments as a tuple or list, and so on), within the constraints of a language whose target environment is native code.

That's not to say it can't be done. It can. However, it's a ton of work, and every attempt to date has struggled with the edge cases and with getting broad adoption.

The convention in Rust, if you need something variadic-ish, is to use a vector or a similar structure. If you want the syntax to read like a function call, write a macro - that's what vec! is, for example: a macro whose body creates a vector, then loops over the macro parameters and generating code to insert each value into the vector, then finishing the generated code with the vector itself so that the whole expression evaluates as you'd expect. Macros like println! and format! do something morally similar, although they rely on compiler magic to interpret the format string argument.

As for your original example, consider the following:

fn main() 
{
    let stuff_to_use = vec![1, 2, 1000];

    let stuff_to_use: Vec<_> = stuff_to_use.into_iter()
        .map(|problem| {
            if problem < 100 {
                2000
            } else {
                problem
            }
        })
        .collect();
    
    println!("{:?}", stuff_to_use);
}

This doesn't quite update the vector in-place the way your original code does, but it preserves a lot of the semantics while avoiding having to borrow and re-borrow the vector of initial values. If mutation in place is important, then consider the following, instead:

fn main() 
{
    let mut stuff_to_use = vec![1, 2, 1000];

    for problem in stuff_to_use.iter_mut() {
        if *problem < 100 {
            *problem = 2000
        }
    }

    println!("{:?}", stuff_to_use);
}

This does use your imperative style, but instead of trying to append the value to a new vector, it uses the &mut i32 problem reference to modify the vector as it goes.

If the intended goal is that vec![1, 2, 1000] becomes vec![1, 2, 1000, 2000, 2000, 1000], them I doubt this can be done without a temporary value to hold the new elements: you can't safely append to a vector while iterating over it, because appending might reallocate the vector, breaking the iterators. Having a vec keep track of its iterators or store enough information that outstanding iterators can follow reallocations is probably not practical.

1 Like

I found having a dedicated Struct type with a derived Default to be a reasonable solution for lack of variadic arguments, when there is potentially a need to pass umpteen of them. A little bit on the verbose side but gets the job done.

(Edit: Of course it doesn’t really replace the variadic arguments, just a very narrow use case that I had. A more general way is probably a macro similar to how format!() does it, but I didn’t look into that).

This sounds resentful of the safety checks Rust is performing. As you say earlier, maybe you shouldn't post after hours of being frustrated, but the safety checks Rust performs are the main point of programming in Rust. If you don't like them but want to stay safe use a GC language. If you don't like them and don't care about undefined behavior, C or C++ are always sitting there. Or maybe you just need to take a little break and remind yourself the reason for choosing Rust in the first place.

It is a bummer, but a fact of life, that a lot of the idioms we've gotten used to in other languages just aren't there in Rust. As other folks have pointed out here, there are ways around all of them. Embrace the change.

1 Like

In most cases, you don't need to create a new Vec to pass a variable number of arguments to a function. There are many other approaches, and in a particular situation, one of them is the best solution. It remains only to gain experience to decide which approache to choose.

// Just call as many times as needed.
// Nice approach because it's simple.
fn fn_stu(stuff: &mut Vec<i32>, x: i32) {
    stuff.push(x);
}

// Why not pass a slice?
// You just need to take a slice from somewhere.
// This is not a problem, just take it from the array at the call site. 
fn fn_stu_slice(stuff: &mut Vec<i32>, xs: &[i32]) {
    stuff.extend_from_slice(xs);
}

// Alternatively, pass the array directly.
fn fn_stu_array<const N: usize>(stuff: &mut Vec<i32>, xs: [i32; N]) {
    stuff.extend(std::array::IntoIter::new(xs));
}

// A more general approach.
// This does not require to create a slice or somehow arrange the arguments in memory sequentially.
// Arguments can be generated from any iterator.
fn fn_stu_iter<I>(stuff: &mut Vec<i32>, xs: I)
where
    I: IntoIterator<Item = i32>,
{
    stuff.extend(xs);
}

// Syntactic approach.
// This is good, but a significant limitation is that a macro cannot be a method. 
macro_rules! fn_stu_macro {
    ($stuff:expr, $($xs:expr),+ $(,)?) => {
        $(fn_stu($stuff, $xs);)+
    }
}

fn main() {
    let mut stuff_to_use = vec![];
    
    fn_stu(&mut stuff_to_use, 0);
    fn_stu_slice(&mut stuff_to_use, &[0, 1, 2]);
    fn_stu_array(&mut stuff_to_use, [0, 1, 2]);
    fn_stu_iter(&mut stuff_to_use, 0..=2);
    fn_stu_macro!(&mut stuff_to_use, 0, 1, 2);
    
    println!("{:?}", stuff_to_use);
}

Of course, this is not the same as variadic arguments. What if you need to pass arguments of different types? This is already a more complex case and requires a good API design. In general, you can cast them to the same type, for example, using an enum.

1 Like

Perhaps, but some of these compiler warnings are not a safety check, but simply a preference check. For example:

non_camel_case_types
non_snake_case
Of these two variables only one produces a warning.

let intValuePassedIntoSecondArray = 0;
let a_a = 0;

Variable or function names are normally down "house style" or programmer preference. I know which variable I would prefer a programmer to use if I'm going back to fix somebody else's code, and it isn't the one the rust compiler accepts.

I've only been using Rust for a year or so, and I'm no expert. Because of this I spend a significant amount of time fighting the compiler. I am only using rust for some trivial projects at the moment, if I needed to do something major then I would use C.

If it sounds resentful I'm sorry, just something that would be a simple implementation in C is taking me a very long time in rust.

Now if there was only a rust compiler warning when people don't use the GNU brace style.... :slight_smile:

Technically, none of Rust's compiler warnings are safety checks (at least in safe Rust). All the safety checks are hard errors, otherwise Rustc would allow safe but unsound code, which is absolutely unacceptable.

I really like that Rust enforces a certain naming style. Although people may not like the specific convention chosen, it provides one really important feature - consistency. Due to this warning, Rust code looks the same everywhere. If you've contributed to one Rust project that knowledge is almost entirely transferrable to any other, and you almost never have to read a style guide. It instantly lowers the bar to entry for every single project using the language.

I would also argue that naming case convention is a feature of a language, just as important as syntax is. For example: Rust, very intentionally, doesn't provide ++ and -- operators. This was a deliberate decision by the language designers about how they think that Rust code should look - just like how the case convention is a deliberate decision by the language designers about how they think Rust code should look. Rust simply draw the line of syntax vs. preference further than a language like C does.

2 Likes

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.