How can I improve this code

Hello,

Is there a way I can improve this code

pub fn build_proverb(list: &[&str]) -> String {
    let mut solution = String::new(); 

    for i in 0 .. list.len() {
      let current = list.get(i).unwrap() ;
      let next = list.get(i + 1); 
      match next {
          Some(v) => {
              let string = format!("For want of a {} the {} was lost.\n", current, v);
              solution.push_str(&string) } 
          None    => {
              let string = format!("And all for the want of a {}.", list.get(0).unwrap());
              solution.push_str(&string) 
          }
      } 

        
    }
    solution
}

Proposed solution:

use std::fmt::Write;

pub fn build_proverb(list: &[&str]) -> String {
    let mut solution = String::new();
    for obj in list.windows(2) {
        let _ = write!(solution, "For want of a {} the {} was lost.\n", obj[0], obj[1]);
    }
    let _ = write!(solution, "And all for the want of a {}.", list[0]);
    solution
}

Notes:

  • write! avoids allocating new strings all the time
  • in general, when not used with strings, it can return errors, so let _ avoids the compiler warning about it
  • list.get(x).unwrap() is just &list[x], and the reference can be omitted since list already contains references.
5 Likes

Thanks for showing this.
I have to find out what windows does exactly. Never seen it before in my first days of learning rust.

and your code fails here :

test test_zero_pieces ... FAILED

failures:

---- test_zero_pieces stdout ----
thread 'test_zero_pieces' panicked at 'index out of bounds: the len is 0 but the index is 0', src\lib.rs:8:63
note: Run with `RUST_BACKTRACE=1` environment variable to display a backtrace.

#[test]
fn test_zero_pieces() {
    let input: Vec<&str> = vec![];
    let expected = String::new();
    assert_eq!(build_proverb(&input), expected);
}

windows gives you an iterator over successive "windows into" the slice, i.e.

        | a | b | c | d |
1st:    ^^^^^^^^^
2nd:        ^^^^^^^^^
3rd:            ^^^^^^^^^

It is often helpful when you have to work on items pairwise or n-wise. Also comes with a sibling called chunks that gives you non-overlapping subslices.

As for the test failure - adapting the code to fit your specification is left to you :slight_smile: but I'd look at the first() method of slices.

1 Like

I hope I can with some 2 / 3 days experience with rust. but I can always try

Please don't teach to ignore errors. Yes, write!() to String never returns error, but it also means if we .unwrap() it will optimized out. Imagine in some time later OP changed solution's type to, say, ArrayString for some performance reason. Obviously write!() to ArrayString can fail, but it will be hard time to debug it if you silently ignored the error.

3 Likes

oke

is there then a better way to improve my code.

Roelof

That's not what I was doing.

oke,

need then some help,

I have this :

pub fn build_proverb(list: &[&str]) -> String {
   let mut solution = String::new();
   match list.slice(0,2).first() {
       Some(v) => { 
           for obj in list.windows(2) {
               let _ = write!(solution, "For want of a {} the {} was lost.\n", obj[0], obj[1]);
           }}
       None => return solution; 
   }
   let _ = write!(solution, "And all for the want of a {}.", list[0]);
   solution
} 

but still type errors on the slice part

@birkenfeld

you mean something like this :

pub fn build_proverb(list: &[&str]) -> String {
    let mut solution = String::new();
    match list.first() {
        Some(_) => { 
            for obj in list.windows(2) {
                let _ = write!(solution, "For want of a {} the {} was lost.\n", obj[0], obj[1]);
            }
             let _ = write!(solution, "And all for the want of a {}.", list[0]);
            }
        None => return solution
    }
    solution
} 

or cam I improve this also ?

Roelof

I mentioned the let _ = write!() part. It doesn't matter for this specific case, but for learners it's hard to distinguish specific cases.

oke, if I delete the let _ part I see this error message

unused `std::result::Result` that must be used
  --> src\lib.rs:10:17
   |
10 |                 write!(solution, "And all for the want of a {}.", list[0]);
   |                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
   |
   = note: this `Result` may be an `Err` variant, which should be handled

so i must use match here also ?

Here is an entirely different solution, which avoids using indexes entirely, by using the split_first function and fold,
passing in the first element as the initial value for prev,
by returning s, it becomes the value for prev for the next iteration.

pub fn build_proverb(list: &[&str]) -> String {
    let mut solution = String::new();
    match list.split_first() {
        Some((first, rest)) => {
            rest.iter().fold(first, |prev, s| {
                let string = format!("For want of a {} the {} was lost.\n", prev, s);
                solution.push_str(&string);
                s
            });
            let string = format!("And all for the want of a {}.", first);
            solution.push_str(&string)
        }
        None => {
            ();
        }
    };
    solution
}

The reason this happens is because you end up indexing into a slice of zero elements, therefore any index (Other than certain ranges) will not work.

What @birkenfeld was talking about .first() method was to use it to make sure we even have an element:

use std::fmt::Write;

pub fn build_proverb(list: &[&str]) -> String {
    let mut solution = String::new();
    for obj in list.windows(2) {
        write!(solution, "For want of a {} the {} was lost.\n", obj[0], obj[1])
            .expect("We should be able to write into a `String`");
    }
    //Here's the difference, we could or could not have a first element...
    if let Some(x) = list.first() {
        write!(solution, "And all for the want of a {}.", x)
            .expect("We should be able to write into a `String`");
    }
    solution
}

This also incorporates the some error protection that was later discussed

sorry, Im now confused. How does now the code looks ?

if I try this :

pub fn build_proverb(list: &[&str]) -> String {
    let mut solution = String::new();
    for obj in list.windows(2) {
        if let Some(x) = list.first() {
            write!(solution, "For want of a {} the {} was lost.\n", obj[0], obj[1]);
        }
        write!(solution, "And all for the want of a {}.", list[0]);
    }        
    solution
} 

I see now 2 error messages

unused variable: `x`
 --> src\lib.rs:6:21
  | Finished dev [unoptimized + debuginfo] target(s) in 0.32s
6 |         if let Some(x) = list.first() {
  |                     ^ help: consider prefixing with an underscore: `_x`
  |
  = note: #[warn(unused_variables)] on by default

warning: unused `std::result::Result` that must be used
 --> src\lib.rs:7:13
  |
7 |             write!(solution, "For want of a {} the {} was lost.\n", obj[0], obj[1]);

Those are Warnings, don't confuse them because one (Errors) means there's a type-level error and the other (Warnings) means that you probably made a logic-level error.

The thing here is that your call to first will return the first element in the case of there being one, which you take and check for. In the case that there is, you ignore the first element and... use the things in obj. That makes no sense, if I were to input something like this:

["Apple", "Banana", "Pear"]

You'd expect to get

For want of a Apple the Banana was lost.
For want of a Banana the Pear was lost.
And all for the want of a Apple.

But this will produce

For want of a Apple the Banana was lost.
And all for the want of a Apple.
For want of a Banana the Pear was lost.
And all for the want of a Apple.

You are interleaving the last sentence every time, it shouldn't be in the loop. Also, the first method call is meant to be for the very last sentence, not the body of the sentences you produce because it is possible that your input is empty, which won't break the iterator (The iterator will simply yield no items in the for loop) but it will break the last one which uses panicking indexing.

sorry, I got more and more confused
I see now :

error[E0308]: mismatched types
  --> src\lib.rs:8:6
   |
8  | /      match list.first(){
9  | |          None => solution,
10 | |          Some(v) => write!(solution, "And all for the want of a {}.", v);
11 | |      }
   | |______^ expected (), found struct `std::string::String`
   |
   = note: expected type `()`
              found type `std::string::String`

on this code :

use std::fmt::Write;

pub fn build_proverb(list: &[&str]) -> String {
    let mut solution = String::new();
    for obj in list.windows(2) {
        let _ = write!(solution, "For want of a {} the {} was lost.\n", obj[0], obj[1]);
    }
     match list.first(){
         None => solution,
         Some(v) => write!(solution, "And all for the want of a {}.", v); 
     }
    
    solution
}

It's the semi-colon instead of coma here:

Some(v) => write!(solution, "And all for the want of a {}.", v)  **;**

Catches me out all the time when returning values from functions without "return", same applies here.

thanks

Still no luck

   Compiling proverb v1.1.0 (C:\Users\roelof\Exercism\rust\proverb)
error[E0308]: match arms have incompatible types
  --> src\lib.rs:10:21
   |
8  | /      match list.first(){
9  | |          None => solution,
   | |                  -------- this is found to be of type `std::string::String`
10 | |          Some(v) => write!(solution, "And all for the want of a {}.", v),
   | |                     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ expected struct `std::string::String`, found enum `std::result::Result`
11 | |      }
   | |______- `match` arms have incompatible types
pub fn build_proverb(list: &[&str]) -> String {
    let mut solution = String::new();
    for obj in list.windows(2) {
        let _ = write!(solution, "For want of a {} the {} was lost.\n", obj[0], obj[1]);
    }
     match list.first(){
         None => solution,
         Some(v) => write!(solution, "And all for the want of a {}.", v),
     }
    
    solution
}

oke, this compilles and alll the tests are green.

use std::fmt::Write;

pub fn build_proverb(list: &[&str]) -> String {
    let mut solution = String::new();
    for obj in list.windows(2) {
        let _ = write!(solution, "For want of a {} the {} was lost.\n", obj[0], obj[1]);
    }
    match list.first() {
         None => solution,
         Some(v) => {write!(solution, "And all for the want of a {}.", v)
            .expect("We should be able to write into a `String`"); solution}
    }
}

I hope this code is allright ?