Type signature for moving resources when possible?

While the most flexible way to write a constructor for an object containing a to-be-initialized collection (such as a Vector, String, etc.) is to use an immutable slice as a parameter, e.g., fn new(string: &str), this seems inefficient at times since quite often (but not always) that slice is just coming from a temporary object that could have been moved directly into the new object. What is the idiomatic way of solving this problem? Surely it is not to write a variant of new for each combination of the arguments that could have been just moved, right?

Usually, the most flexible way is to accept T: Into<U>, where U is the real type the constructor needs (for example, T: Into<String>).

Your suggestion worked well for strings, but how about the situation where I want to initialize a vector from either another temporary vector (moving) or from a slice of an array (copying). I am getting the error:

the trait std::convert::From<&[{integer}; 5]> is not implemented for std::vec::Vec<u8>

This is the code I was using:

struct Collection {
   data: Vec<u8>,
}

impl Collection {
   fn new<D: Into<Vec<u8>>>(data: D) -> Collection {
      Collection {
         data: data.into()
      }
   }
}

fn main() {
    let v : Vec<u8> = vec![1,2,3,4,5];
    let s = [2,3,4,5,6];
    
    let c = Collection::new(v);
    let c2 = Collection::new(&s);   
}

Am I missing something?

That's the limitation of the current arrays implementation. You'll have to convert it into slice:

fn main() {
    let s = [2,3,4,5,6];
    let c2 = Collection::new(&s[..]);   
}

Playground

Thanks @Cerberuser , I have one last follow up question. Considering a String example of the above as follows:

#[derive(Debug)]
struct Sentence {
   text: String,
}

impl Sentence {
   fn new<T: Into<String>>(text: T) -> Sentence {
      Sentence {
         text: text.into()
      }
   }
}

fn main() {
    let str1 : &str = "hello";
    let str2 : String = String::from("world");
    
    let sntc1 = Sentence::new(str1); // copy from str1
    let sntc2 = Sentence::new(str2); // consume and use str2 resources
    
    println!("{:?}", sntc1);
    println!("{:?}", sntc2);
}

Considering that String implements Deref<Target=str>, how can we be sure that (i) the memory on the heap for str2 is now used by sntc2.text and conversely that (ii) str2 was not deref'd into a string slice which was then copied with the original memory behind str2 being dropped afterwards?

You can be sure that str2 is not first converted to a &str because

  1. A String will never coerce into a &str, only a &String can do that.

  2. Even if you did write &str2, it will not coerce to a &str because new is generic, and as noted here

    Any location that is explicitly typed will cause a coercion to its type. If inference is necessary, the coercion will not be performed.

    and your new method is generic, which would have fallen in the "inference" case.

Right, so the implementation of from being used in the case of str2 must be the blanket implementation impl<T> From<T> for T which basically just moves/transfers ownership?

Yes, that is the implementation in use here.

Ok, one last example...

I am curious as to what Rust is capable of in terms of efficiently taking ownership when possible for the case where the resource is inside a collection whose ownership could be taken directly. The best I have come up with so far is the following:

#[derive(Debug)]
struct Sentence {
   words: Vec<String>,
}

impl Sentence {
   fn new<T>(words: T) -> Sentence
      where T: IntoIterator,
            T::Item : Into<String> {
      Sentence {
         words: words.into_iter().map(|w| w.into()).collect()
      }
   }
}

fn main() {
    let w1 : Vec<&str> = vec!["foo","bar"];
    let w2 : [&str; 2] = ["foo","bar"];
    let w3 : Vec<String> = vec![String::from("foo"), String::from("bar")];
    let w4 : [String; 2] = [String::from("foo"), String::from("bar")];
  
    let s1 = Sentence::new(w1); // copy
    //let s2 = Sentence::new(&w2[..]); // copy
    let s3 = Sentence::new(w3); // transfer ownership of whole vector
    //let s4 = Sentence::new(&mut w4[..]); // transfer ownership of individual strings??
    
    println!("{:?}", s1);
    //println!("{:?}", s2);
    println!("{:?}", s3);
    //println!("{:?}", s4);        
}

This is not a good solution since (i) it does not work with slices and (ii) in the case of w3 and s3, I would prefer to transfer ownership of the vector itself into the structure instead of just transferring ownership of the contents one by one. Is it possible to achieve this with an appropriate set of traits?

Another way is just to be explicit. If the function unconditionally needs the owned String in its body, it would be preferable to take an owned String itself as parameter, and let the caller do allocation if needed.

1 Like

It is not possible to support both Vec<&str> and Vec<String> if you want the latter to reuse the allocation in the vector. You should probably just take a Vec<String> at this point.

This makes sense, if the struct needs to own its resources, the most straight forward way of doing this is just creating those resources at the call site via string::to_owned or an equivalent function.

@alice could you quickly elaborate on why it isn't possible to support both Vec<&str> and Vec<String> if I want the ownership of the latter transferred? Is there something logically wrong here or is this just a limitation of the trait system?

It's because the thing that converts the vector would somehow have to know whether the conversion of the item is a no-op or not. I suppose it might be possible with the nightly-only specialization feature if you made your own conversion trait.

There are some specialization stuffs in the standard library, e.g. vec.into_iter().collect() will actually reuse the allocation, but your map breaks this optimization.

1 Like

Is there another way to call into on the contents of the vector/slice without map that does not break this optimization?

Not in the standard library, no. But as I said, you could probably do it with nightly-only specialization and a custom conversion trait.

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.