Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What does “`str` does not have a constant size known at compile-time” mean, and what's the simplest way to fix it?

I'm trying to manipulate a string derived from a function parameter and then return the result of that manipulation:

fn main() {
    let a: [u8; 3] = [0, 1, 2]; 
    for i in a.iter() {
        println!("{}", choose("abc", *i));
    }
}

fn choose(s: &str, pad: u8) -> String {
    let c = match pad {
        0 => ["000000000000000", s].join("")[s.len()..],
        1 => [s, "000000000000000"].join("")[..16],
        _ => ["00", s, "0000000000000"].join("")[..16],
    };
    c.to_string()
}

On building, I get this error:

error[E0277]: the trait bound `str: std::marker::Sized` is not satisfied
 --> src\main.rs:9:9
  |
9 |     let c = match pad {
  |         ^ `str` does not have a constant size known at compile-time
  |
  = help: the trait `std::marker::Sized` is not implemented for `str`
  = note: all local variables must have a statically known size

What's wrong here, and what's the simplest way to fix it?

like image 319
Brent.Longborough Avatar asked Mar 20 '18 20:03

Brent.Longborough


1 Answers

TL;DR Don't use str, use &str. The reference is important.


The issue can be simplified to this:

fn main() {
    let demo = "demo"[..];
}

You are attempting to slice a &str (but the same would happen for a String, &[T], Vec<T>, etc.), but have not taken a reference to the result. This means that the type of demo would be str. To fix it, add an &:

let demo = &"demo"[..];

In your broader example, you are also running into the fact that you are creating an allocated String inside of the match statement (via join) and then attempting to return a reference to it. This is disallowed because the String will be dropped at the end of the match, invalidating any references. In another language, this could lead to memory unsafety.

One potential fix is to store the created String for the duration of the function, preventing its deallocation until after the new string is created:

fn choose(s: &str, pad: u8) -> String {
    let tmp;

    match pad {
        0 => {
            tmp = ["000000000000000", s].join("");
            &tmp[s.len()..]
        }
        1 => {
            tmp = [s, "000000000000000"].join("");
            &tmp[..16]
        }
        _ => {
            tmp = ["00", s, "0000000000000"].join("");
            &tmp[..16]
        }
    }.to_string()
}

Editorially, there's probably more efficient ways of writing this function. The formatting machinery has options for padding strings. You might even be able to just truncate the string returned from join without creating a new one.


What it means is harder to explain succinctly. Rust has a number of types that are unsized. The most prevalent ones are str and [T]. Contrast these types to how you normally see them used: &str or &[T]. You might even see them as Box<str> or Arc<[T]>. The commonality is that they are always used behind a reference of some kind.

Because these types don't have a size, they cannot be stored in a variable on the stack — the compiler wouldn't know how much stack space to reserve for them! That's the essence of the error message.

See also:

  • What is the return type of the indexing operation?
  • Return local String as a slice (&str)
  • Why your first FizzBuzz implementation may not work
like image 51
Shepmaster Avatar answered Nov 19 '22 07:11

Shepmaster