Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

hint (or directions?) for move_semantics2 are confusing/wrong? #1596

Closed
solarshado opened this issue Jul 16, 2023 · 10 comments
Closed

hint (or directions?) for move_semantics2 are confusing/wrong? #1596

solarshado opened this issue Jul 16, 2023 · 10 comments

Comments

@solarshado
Copy link

Working my way through 5.5.1, and the hint for move_semantics2 suggests multiple solutions. However, unless I've misunderstood something, neither of the first two suggestions produce the "expected output" described in the comments above main(); they both copy vec0 before it's populated, leaving it empty when printed.

As far as I can tell, the only suggestion that does work is the third (mutably borrow a reference to vec0), which, contradictory to what the hint says, is the only option which doesn't affect the output of the first println!.

@EnderWolf50
Copy link

I'm currently doing this and get extemely confused too. 😕

@JoachimT99
Copy link

The current version of the exercise tries to teach both the concept of moving values as well as how to use references. I think the goal of the exercise is unclear because it is trying to teach too much within a single exercise. As @solarshado points out, the difference in behavior is unfortunate.

Could the exercise be split into two? One exercise could teach how to pass ownership, such as in listing 4-4. While the other could teach how to pass arguments as references to functions such as in chapter 4.2 of the book.

@themainframe
Copy link

Adding another confused voice to this. Hint 1 - "Make another, separate version of the data that's in vec0 and pass that to fill_vec instead." directly results in the stated expected output at the top of the kata not being satisfied (since vec0 will remain empty).

This one needs a rethink. Too much going on, needs to focus on the concept of moving more.

@jameskr97
Copy link

jameskr97 commented Aug 17, 2023

Hello, First time rust writer, long time programmer. I thought I'd add add some code references for context, as I also found myself to be a little lost with this problem as-well.

For reference, here is the original problem

// move_semantics2.rs
//
// Expected output:
// vec0 has length 3, with contents `[22, 44, 66]`
// vec1 has length 4, with contents `[22, 44, 66, 88]`
//
// Execute `rustlings hint move_semantics2` or use the `hint` watch subcommand
// for a hint.
// I AM NOT DONE
fn main() {
let vec0 = Vec::new();
let mut vec1 = fill_vec(vec0);
println!("{} has length {}, with contents: `{:?}`", "vec0", vec0.len(), vec0);
vec1.push(88);
println!("{} has length {}, with contents `{:?}`", "vec1", vec1.len(), vec1);
}
fn fill_vec(vec: Vec<i32>) -> Vec<i32> {
let mut vec = vec;
vec.push(22);
vec.push(44);
vec.push(66);
vec
}

Here are the hints associated:

rustlings/info.toml

Lines 294 to 314 in 464cb55

[[exercises]]
name = "move_semantics2"
path = "exercises/move_semantics/move_semantics2.rs"
mode = "compile"
hint = """
When running this exercise for the first time, you'll notice an error about
"borrow of moved value". In Rust, when an argument is passed to a function and
it's not explicitly returned, you can't use the original variable anymore.
We call this "moving" a variable. When we pass `vec0` into `fill_vec`, it's being
"moved" into `vec1`, meaning we can't access `vec0` anymore after the fact.
Rust provides a couple of different ways to mitigate this issue, feel free to try them all:
1. You could make another, separate version of the data that's in `vec0` and pass that
to `fill_vec` instead.
2. Make `fill_vec` borrow its argument instead of taking ownership of it,
and then copy the data within the function (`vec.clone()`) in order to return an owned
`Vec<i32>`.
3. Or, you could make `fill_vec` *mutably* borrow a reference to its argument (which will need to be
mutable), modify it directly, then not return anything. This means that `vec0` will change over the
course of the function, and makes `vec1` redundant (make sure to change the parameters of the `println!`
statements if you go this route)
"""

If I understand the problem correctly, the author wants the reader to consider how the problem could be solved with and without changing the signature of the original fill_vec. I found 1 (somewhat) and 3 to be the only hints that (for me, a new rust developer) makes sense.

For 1: Point 1 says to "Make another, separate version of the data that's in vec0 and pass that to fill_vec instead.", though vec0 starts with no data, and data is only added in the fill_vec function. The two solutions I could think of that fell in line with making "another separate version of the data" was the following:

// Possibility 1: two disconnected variables (I think this is what the author intended?)
 fn main() {
     let vec0 = fill_vec(Vec::new()); 
     let mut vec1 = fill_vec(Vec::new());  // the rest of the file remains unchanged
}
// Possibility 2: slicing
// This doesn't fit - `vec0` shadows itself and depends on `vec1` for it's values. I believe
// `fill_vec` is intended to be the only function adding to the `vec0`
fn main() {
     let vec0 = Vec::new(); 
     let mut vec1 = fill_vec(Vec::new());
     let vec0 = &[0..3]; // the rest of the file remains unchanged
}

Whatever the authors intended solution was, I do think clarity in the first hint would be beneficial.

Hint 2: It seems to suggest taking a non-mutable reference into fill_vec, the modifying it, and sending a vec.clone() to vec1. To the best of my understanding of rust ownership, I don't think that this one is asking for is possible, though again, I am a new rust dev.

Hint 3: This hint seems to be identical to what move_semantics3.rs asks the user to do. Unless I'm missing something from this hint, I think it could be removed altogether.

I hope I made the confusion I (and maybe the others) was having more clear. I'll finish on a note that this repo is directed towards new developers, so I'm sure all of us using it to learn would appreciate an experienced dev chiming in here.

@JoachimT99
Copy link

I actually think the best solution is to just return ownership of the vector. The task can be simplified by only having a single variable as shown below:

fn main(){
  let mut vec0 = Vec::new();
  vec0 = fill_vec(vec0);
  //...
}

The starting point could have a call to fill_vec without capturing the return value, which would trigger the appropriate compiler error.

fn main(){
  let mut vec0 = Vec::new();
  fill_vec(vec0);
  //...
}

I can submit a PR with these changes. More work might be needed to ensure the exercises flow nicely together.

@ryanwhitehouse
Copy link
Contributor

@JoachimT99 Yeah.. that looks much better!

@shadows-withal
Copy link
Member

Closing this cautiously because #1660 got merged. Do let me know if those exercises are still too difficult/unintelligble!

@jpnarkinsky
Copy link

l_vec function. The two solutions I could think of that fell in line with making "another separate version of the data" was the follow

For what it's worth... the part I found extremely confusing was that I needed to modify the test/calling-code, not just the function. I do think better hints would help.

@ajlashford
Copy link

ajlashford commented Dec 25, 2023

Should we be modifying the test?

@andrewsuperlegit
Copy link

andrewsuperlegit commented Jun 10, 2024

My solution to this for the first hint was to do:

    let vec1 = fill_vec(vec0.clone());

and trying to do the second hint which I'm SUPER confused by:

fn main() {
    let vec0 = vec![22, 44, 66];
    let vec1 = fill_vec(&vec0);
    assert_eq!(vec0, vec![22, 44, 66]);
    assert_eq!(vec1, vec![22, 44, 66, 88]);
}

fn fill_vec(vec: &Vec<i32>) -> Vec<i32> {
    let mut vec = vec;
    let mut vec = vec.to_vec();
    vec.push(88);
    vec
}

which... I don't know if it's right or what it's supposed to be... but it works. honestly the errors that i got from the compiler while trying to do the second one were super confusing because they kept telling me to change function signature stuff, and then once I did it kept complaining about types differing in mutability originally:

fn fill_vec(vec: &Vec<i32>) -> Vec<i32> {
    let mut vec:&mut Vec<i32> = vec;
    vec.push(88);
    vec.to_vec()
}

So yeah. just thought I'd let you know my experience with this bc this is the first exercise I've found to be confusing.

That said, rustlings rules. Great way to learn. 10/10 so far

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

10 participants