To which scope is a function's returned value sent?

Hello,

The problem :

Here is a test program :

fn test(mut n1:i8)->i8 {
    n1 += 10;
    println!("{n1}");
    n1
}

fn main() {

    let a:i8 = 4;
    
    test(a);
    
    println!("{a}");
    
}

In the fn main() function, the variable a is declared as an i8 container.

The value 4 was then assigned to the variable a.

a was then entered as an argument inside the function test()

The function test() was expected get ownership of the variable a aswell as returning a as a mutable variable with a different value. Here a was expected to have 14 as a value, instead in remained with 4, as shown by the println!.

The question :

If the function test(a) really took ownership of the variable a, then how does println!("{a}") not display 14 ? How can it still refer to 4 ?

This implies the question, to which scope is a function's returned value sent ? Is it sent to the scope of the function ? Or is it sent to the scope that the function is in ?

Besides, when I use the following program, which is the same, I get an error for doing the same thing that worked before. I can't see any difference.


fn string_uppercase(mut data: String) {
    data = data.to_uppercase();

    println!("{data}");
}


fn main() {
    let data = "Rust is great!".to_string();

    string_uppercase(data);
    
    println!("{data}");
}


In this second program, I get an error saying that the value was borrowed after being moved. It's the same structure as the former program. Yet one works in an unintended way, and the other gives an error. I clearly miss what is the difference between the 2 programs.

Thanks and have a good day

The answer is that some types (like integers) implement the Copy trait, which allows move-by-copy, retaining the original ownership while also creating a copy that has a new owner.

String does not implement Copy. Moving a String requires a transfer of ownership. Thus the error.

2 Likes

Let's walk through this program. The first thing that happens is this line:

Which creates a variable a of type i8 with the value 4.

Then you call test, passing a to it. Since i8 is Copy, test() is called with a copy of the variable a.

Then test begins executing. Its parameter (which is 4_i8) is put into a local variable n1. This variable is local to the test function.
test then adds 10 to its local n1, prints it, and then returns it. We return to main(), but what happens to the return value of test?

Well, since there's a semicolon after the expression test(a), the return value from test is dropped and simply ignored.

The original a value is not affected by any of this, and so printing a shows 4.


In your program using String, the difference is the previously mentioned Copy trait. Strings are not implicitly copied, so the string in data is moved into string_uppercase(), which means that the data variable cannot be used because the string has been moved.

string_uppercase uppercases the string it was given, but since it was moved into a local, the string is dropped at the end of string_uppercase.

The println! fails because data has been moved out of, so it cannot be accessed.

3 Likes

And to fix your code, you'll probably want to use a reference to borrow the variable from an upper scope:

fn string_uppercase(data: &mut String) {
    *data = data.to_uppercase();
    println!("{data}");
}

fn main() {
    let mut data = "Rust is great!".to_string();

    string_uppercase(&mut data);

    println!("{data}");
}

And now it successfully screams at me about how "RUST IS GREAT! RUST IS GREAT!"

Also see chapter 4 of the Book.

2 Likes

Thank you for the answer

Thank you very much for this detailed answer.

I would really like to give to test() the ownership of a and then display the newly owned a after the execution of the test() function. Would it be possible ?

For now, the only thing I could do was this :

fn test(mut n1:i8)->i8 {
    n1 += 10;
    println!("{n1}");
    n1
}

fn main() {

    let mut a:i8 = 4;
    
    a = test(a);
    
    println!("{a}");
    
}

Which works, however, I wanted to do something like this instead :

fn test(mut n1:i8)->i8 {
    n1 += 10;
    println!("{n1}");
    n1
}

fn main() {

    let a:i8 = 4;
    
    test(a)
    
    println!("{a}");
    
}

Which does not work because main() does not allow to return anything (from what I understood by searching on the web).

So how could I change an immutable variable by transfering it's ownership to a function and make the function return it with the same name (for it to be displayed after being modified by the said function for example).

to be more precise, here would be the pseudo-program for what I was aiming :

fn main() {
let a = something

print(a) // displays something

change_to_something_else(a)

print(a) // displays something else



}

I really don't know if that's even possible.

Thanks for the help

Thanks for the fix, the screams are directly from rustlings

You could shadow the name a if you really need a to be immutable:

fn test(mut n1:i8)->i8 {
    n1 += 10;
    println!("{n1}");
    n1
}

fn main() {
    let a: i8 = 4;

    let a = test(a);

    println!("{a}");
}

But this declares an entirely new variable a, with the only connection being that it has the same name as the previous a.

Most of the time, a &mut is the solution to letting a value get modified by a function, though: To which scope is a function's returned value sent? - #4 by cod10129.

1 Like

Thanks for the answer, shadowing was the thing I wanted to also avoid. This is because I was wondering if it was possible to do this by using a function to modify the original variable without shadowing it.

I added the pseudo-program lately in the former answer, it would be something like this :


fn main() {
let a = something

print(a) // displays something

change_to_something_else(a)

print(a) // displays something else

}

I really wonder if it would be possible, to give ownership of a variable to a function, then have this said function return the variable in the same scope as the original variable.

For this, function must have a mutable borrow of this variable - essentially by definition.

1 Like

Thanks for the answer, now I know that such a thing would be impossible without having the original variable being mutable.

For the sake of completion, this is what you want:

fn test(n1: &mut i8)->i8 {
    *n1 += 10;
    *n1
}

fn main() {
    let mut a: i8 = 4;

    test(&mut a);

    println!("{a}");
}
2 Likes

Thank you very much for this answer, I didn't even know that using * from inside a function in such a way was possible. That's indeed the closest to what I wanted to do.

I tried to modify to see if it could work without mutabilising the initial variable and it gave this :

fn test(&mut n1)->i8 {
    *n1 += 10;
    *n1
}

fn main() {
    let a: i8 = 4;

    test(a);

    println!("{a}");
}

Which gave an error, it is really impossible to make the immutable mutable, even if we transfer ownership to a function. This is very strange as even changing ownership is not above the initial immutable aspect of the declared variable.

Thanks again for this answer and have a very good day

But you don't. At least, the function doesn't allow you to, since it accepts a borrow, not an owned value.

1 Like

If you want to get rid of the &mut at the call to test, you can declare the variable a to be of type &mut i8:

fn test(n1: &mut i8)->i8 {
    *n1 += 10;
    *n1
}

fn main() {
    let a: &mut i8 = &mut 4;

    test(a);

    println!("{a}");
}
1 Like

Thank you very much for this answer, in this example, if I understand well, the variable a contains an exclusive reference to a value. That reference is not the value itself, therefore, *n1 would refer to the mutable value 4 and not a's value which would remain the same immutable exclusive reference to a mutable value. I probably didn't understand why there is &mut i8 and &mut 4 so I'll have to make tests with this. I however wonder if the last *n1 (the return value of test() would be necessary as I believed the return value was ignored due to the presence of a semi-colon at test(a);

After some tests, this version also worked (instead of let a:&mut i8 = &mut 4; there is let a = &mut 4;) :

fn test(n1:&mut i8) {
    *n1 += 10;
    
}

fn main() {
    let a = &mut 4;

    test(a);

    println!("{a}");
}

Thank you and have a very good day

1 Like

Just to clarify, it's not so much the semicolon that does this, as it's the fact that it's neither assigned to anything, not returned from the function. A function body (and any other block) is essentially a semicolon separated list of expressions and assign statements, where the last one becomes the return value:

{ let mut a = 4 ; test(&mut a) ; a } // works with a and returns it

When you put a semicolon at the end of the block, an implicit unit expression is added as the return expression by the compiler:

{ let mut a = 4 ; test(&mut a) ; println!("{a}") ; }

// ...is essentially...

{ let mut a = 4 ; test(&mut a) ; println!("{a}") ; () }

The exception to this is control flow expressions (if, for, etc.) that don't need the senicolon unless they are used in assignment or expressions that do require it.

You don't need to memorize all the details at this point, but I wanted to point it out so it doesn't cause any misconceptions. :slightly_smiling_face:

2 Likes

Thank you very much for these detailed informations. It is very useful for me and probably other people to know these things as it helps me and probably other people to avoid confusion.

Thanks and have a very good day

1 Like