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 forstd::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[..]);
}
Thanks @Cerber-Ursi , 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
-
A
String
will never coerce into a&str
, only a&String
can do that. -
Even if you did write
&str2
, it will not coerce to a&str
becausenew
is generic, and as noted hereAny 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.
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.
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.