Deref coercion from String to &str doesn't seem to always work

ded41ba
Opened by Val Markovic at 2023-11-18 18:30:20

This code compiles fine:

fn main() {
  let x = "a".to_string();
  let y: String = ["b", &x[..], "c"].concat();
  println!("{}", y);
}

but this code doesn't

fn main() {
  let x = "a".to_string();
  // Doesn't work
  let y: String = ["b", &x, "c"].concat();
  println!("{}", y);
}

error output:

<anon>:4:25: 4:27 error: mismatched types:
 expected `&str`,
    found `&collections::string::String`
(expected str,
    found struct `collections::string::String`) [E0308]
<anon>:4   let y: String = ["b", &x, "c"].concat();
                                 ^~

I'd expect to not have to sometimes write &foo[..] and sometimes &foo to get a &str out a String. The longer form is too verbose, and the inconsistency of having to sometimes use one over the other seems like a needless user mental model cost, especially because it's not obvious to me at all when I should use one form over the other.

rustc version: rustc 1.0.0-nightly (522d09dfe 2015-02-19) (built 2015-02-21)

  1. cc @eddyb

    Jorge Aparicio at 2015-02-21 22:44:56

  2. Another example, with Vector<T> and [T]. This works:

    fn main() {
      let x = vec![97u8];
      assert_eq!(b"a", &x[..]);
    }
    

    But this fails to compile:

    fn main() {
      let x = vec![97u8];
      assert_eq!(b"a", &x);
    }
    

    Output:

    <std macros>:5:10: 5:35 error: the trait `core::cmp::PartialEq<collections::vec::Vec<u8>>` is not implemented for the type `[u8]` [E0277]
    <std macros>:5 if ! ( ( * left_val == * right_val ) && ( * right_val == * left_val ) ) {
                            ^~~~~~~~~~~~~~~~~~~~~~~~~
    <std macros>:1:1: 9:39 note: in expansion of assert_eq!
    <anon>:3:3: 3:24 note: expansion site
    <std macros>:5:43: 5:68 error: the trait `core::cmp::PartialEq<[u8]>` is not implemented for the type `collections::vec::Vec<u8>` [E0277]
    <std macros>:5 if ! ( ( * left_val == * right_val ) && ( * right_val == * left_val ) ) {
                                                             ^~~~~~~~~~~~~~~~~~~~~~~~~
    <std macros>:1:1: 9:39 note: in expansion of assert_eq!
    <anon>:3:3: 3:24 note: expansion site
    error: aborting due to 2 previous errors
    playpen: application terminated with error code 101
    

    I have tons of other examples in my codebase. Like, 30+.

    Val Markovic at 2015-02-21 23:13:55

  3. (FWIW, that second example could be written as assert_eq!(b"a", vec![97u8]) because PartialEq is implemented between &[u8] and Vec<u8>, no need for deref coercions nor slicing syntax)

    Jorge Aparicio at 2015-02-21 23:35:49

  4. The problem also appears here:

    fn main()
    {
        let rs = "toto";
        let s = rs.to_string();
    
        // That works as expected, &s coerces s to type &str
        fn titi(s:&str) { assert_eq!(s,"toto");};
        titi(&s);
    
        // That also works, same coercion happens
        let cs:&str = &s;
        assert_eq!(cs,rs);
    
        // The following expression fails to run, as though the coercion did not happen
        let cs = &s;
        assert_eq!(cs,rs);
    }
    

    yann-ledu at 2015-02-26 13:09:49

  5. This is somewhat similar to https://github.com/rust-lang/rust/issues/16864.

    I think @nrc was doing some work on coercions recently?

    Huon Wilson at 2016-01-08 06:35:29

  6. I can confirm the issue is still present on rustc 1.8.0-nightly (57c357d89 2016-02-16). My testcase still repros.

    Val Markovic at 2016-02-21 01:37:47

  7. Both arrays and the expansion of assert_eq fail to trigger coercions. The former is easier to fix, try changing this call to be to check_expr_coercable_to_type instead. That should make it so the first array element with even a partially inferrable type triggers coercions on subsequent array elements. The perfect solution would involve smarter unification such that &String followed by &str goes back and coerces the &String (or @nikomatsakis' order-independent type-checking).

    Eduard-Mihai Burtescu at 2016-02-21 04:24:23

  8. Interesting developments: the first test case I posted now compiles fine.

    fn main() {
      let x = "a".to_string();
      // Doesn't work
      let y: String = ["b", &x, "c"].concat();
      println!("{}", y);
    }
    

    But the second test case fails compilation:

    fn main() {
      let x = vec![97u8];
      assert_eq!(b"a", &x);
    }
    

    Here's the error message:

    error[E0277]: the trait bound `[u8; 1]: std::cmp::PartialEq<std::vec::Vec<u8>>` is not satisfied
     --> <std macros>:5:6
      |
    5 | if ! ( * left_val == * right_val ) {
      |      ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    foo.rs:3:3: 3:24 note: in this expansion of assert_eq! (defined in <std macros>)
      |
      = help: the following implementations were found:
      = help:   <[A; 0] as std::cmp::PartialEq<[B; 0]>>
      = help:   <[A; 0] as std::cmp::PartialEq<[B]>>
      = help:   <[B] as std::cmp::PartialEq<[A; 0]>>
      = help:   <[A; 0] as std::cmp::PartialEq<&'b [B]>>
      = help: and 228 others
      = note: required because of the requirements on the impl of `std::cmp::PartialEq<&std::vec::Vec<u8>>` for `&[u8; 1]`
    
    error: aborting due to previous error
    

    rustc version: rustc 1.12.0 (3191fbae9 2016-09-23).

    Val Markovic at 2016-10-02 03:16:37

  9. such that &String followed by &str goes back and coerces the &String

    FWIW this is what was implemented and why the array case works now.

    Eduard-Mihai Burtescu at 2016-10-02 03:38:43

  10. Here is another case with a trait, wich fails to compile:

    
    fn main() {
    
        concat("nice", "cooel");
        concat("nice".to_string(), "cooel");
        let yop = "nice".to_string();
        concat(&yop, "cooel"); // need to add manually "as &str"
        
    }
    
    pub fn concat<S: Into<String>>(path: S, suffix: &str) -> String {
        path.into() + suffix
    }
    

    PSeitz at 2018-02-14 10:44:36

  11. Also ran into this, in a very simple case:

    works:

    fn prompt_and_validate(msg: &str, options: &[&str]) {
      let reply = prompt(msg);
      let r: &str = &reply;
      if options.contains(&r) {
        
      }
    }
    

    doesn't work:

    fn prompt_and_validate(msg: &str, options: &[&str]) {
      let reply = prompt(msg);
      let r = &reply;
      if options.contains(&r) {
        
      }
    }
    

    or in other words, double deref is not working automatically:

    fn prompt_and_validate(msg: &str, options: &[&str]) {
      let reply = prompt(msg);
      if options.contains(&&reply) {
        
      }
    }
    

    with

    mismatched types
    
    expected str, found struct `std::string::String`
    
    note: expected type `&&str`
             found type `&&std::string::String`
    

    (workaround is &reply.as_str())

    Michal Srb at 2019-04-21 22:44:58

  12. This still seems to fail today in Rust 1.55:

    fn main() {
        let cmd = "some command".split_whitespace().collect::<Vec<_>>();
        match &cmd {
            ["some", sub] => println!("some {}", sub),
            ["quit"] => println!("bye!"),
            _ => println!("oops"),
        }
    }
    

    with:

    error[E0529]: expected an array or slice, found `Vec<&str>`
      --> src/main.rs:13:9
       |
    13 |         ["some", sub] => println!("some {}", sub),
       |         ^^^^^^^^^^^^^ pattern cannot match with input type `Vec<&str>`
    
    error[E0529]: expected an array or slice, found `Vec<&str>`
      --> src/main.rs:14:9
       |
    14 |         ["quit"] => println!("bye!"),
       |         ^^^^^^^^ pattern cannot match with input type `Vec<&str>`
    
    For more information about this error, try `rustc --explain E0529`.
    error: could not compile `playground` due to 2 previous errors
    

    Exchanging &cmd for &*cmd or &cmd[..] makes it work. Is this intended or really a bug that the coercion does not trigger here?

    Rogério Almeida at 2021-09-17 21:24:49

  13. It's a missing feature: we could try to figure out some kind of "type skeleton" from the patterns, before type-checking the expression being matched, but we don't today.

    That is, today the information flows only the other way around: from the expression to the patterns.

    Eduard-Mihai Burtescu at 2021-09-20 21:53:14

  14. we could try to figure out some kind of "type skeleton" from the patterns, before type-checking the expression being matched, but we don't today

    @eddyb Is this still something we might want to do?

    pierwill at 2023-08-29 19:38:45