This question already has answers here:
How to match a String against string literals?
(8 answers)
What are the differences between Rust's `String` and `str`?
(14 answers)
Closed 4 years ago.
I'm new to Rust (1.31) and I would like to understand a simple piece of code that does not compile:
fn main() {
s = String::from("foo");
match s {
"foo" => {
println!("Yes");
}
_ => {
println!("No");
}
}
}
The error associated is :
10 | "foo" => {
| ^^^^^ expected struct `std::string::String`, found reference
After this error, I decided to change the code to :
fn main() {
let s = String::from("foo");
match s {
String::from("foo") => {
println!("Yes");
}
_ => {
println!("No");
}
}
}
By doing so, I was hoping to have the correct type, but it is not the case :
10 | String::from("foo") => {
| ^^^^^^^^^^^^^^^^^^^ not a tuple variant or struct
I am quite puzzled with this message from the compiler, at the end I managed to make it work by implementing :
fn main() {
let s = String::from("foo");
match &s as &str {
"foo" => {
println!("Yes");
}
_ => {
println!("No");
}
}
}
However, I do not understand the underlying mechanisms that make this solution the right one and why my second example does not work.
The first example doesn't work, because s is of type String, which is a string variant that owns the data in it. It is matched against a string literal (which can be be used as type &str). match doesn't understand how to compare those two different types, so it errors.
However String dereferences to &str, by implementing Deref<Target=str>, which means references to String can be used where a &str is required, e.g. for comparing it to one. That's what happens in the third example. By creating the reference &s, the implicit deref can happen, and the two types get comparable.
You can achieve the same thing with a little less magic by using the explicit method which creates a &str from String:
fn main() {
let s = String::from("foo");
match s.as_str() {
"foo" => {
println!("Yes");
},
_ => {
println!("No");
}
}
}
The second example tries to make things comparable making String the common type instead of &str. It doesn't work, because match expects a pattern on the left side, and not a function call which creates a new struct (and which also allocates behind the scene). Even if it would work (e.g. by moving the String creation outside of the match), the approach would be less desirable, because the new String would require a memory allocation.
Related
This question already has answers here:
How do I match based on a dynamic variable?
(4 answers)
How can I store a pattern in a variable in Rust?
(1 answer)
Closed 8 months ago.
Let's look into the following code:
struct MyStruct {
field: String,
key1: String,
key2: String,
}
impl MyStruct {
fn match_test(&self, x: String) {
match x {
self.key1 => {
},
self.key2 => {
},
_ => {}
};
}
}
it would yield a compilation error:
error: expected one of `!`, `(`, `...`, `..=`, `..`, `::`, `=>`, `if`, `{`, or `|`, found `.`
--> ./ex_067.rs:10:17
|
10 | self.key1 => {
| ^ expected one of 10 possible tokens
So this post boils down to the question as to how to match smth among varname.field ?
I imagine that is it possible to solve inverting the actual matching thing in such a way:
match self {
MyStruct { key1, .. } if *key1 == x => {},
MyStruct { key2, .. } if *key2 == x => {},
_ => {}
};
Still and all, it imposes to much boilerplate code, on top of inversion.
So, I wonder, if it can be done concise and straightforward ?
How to get rid of inversion (move to match x {} from match self {}) and if stuff, which makes quite a lot of boiler plate code.
It looks like you may be trying to mix the concept of a switch (from other languages) vs Rust's match, which is pattern matching as opposed to conditionals based on the data (although those conditionals are possible via match guards.
Your first example fails because you are trying to match on a value, which is how switch statements work it nearly every other language. In Rust, the match takes a pattern to bind/destructure your variable to. In this case, you would want to match on any x, and then execute the arm which satisfies your self.keyn == x guard:
struct MyStruct {
field: String,
key1: String,
key2: String,
}
impl MyStruct {
fn match_test(&self, x: String) {
match x {
val if self.key1 == x => {
println!("{val} is key1");
},
val if self.key2 == x => {
println!("{val} is key2");
},
_ => {}
};
}
}
This may seem like a lot of boilerplate, but match statements are super useful with their ability to match inside of enum variants and to destructure other types, and this is just one case where they might not feel the most ergonomic.
How do I match against a nested String in Rust? Suppose I have an enum like
pub enum TypeExpr {
Ident((String, Span)),
// other variants...
}
and a value lhs of type &Box<TypeExpr>. How do I check whether it is an Ident with the value "float"?
I tried
if let TypeExpr::Ident(("float", lhs_span)) = **lhs {}
but this doesn't work since TypeExpr contains a String, not a &str. I tried every variation of the pattern I could think of, but nothing seems to work.
If you really want to do this with an if let, you might have to do it like this
if let TypeExpr::Ident((lhs_name, lhs_span)) = lhs {
if lhs_name == "float" {
// Do the things
}
}
Of course, it can also be done with a match:
match lhs {
TypeExpr::Ident((lhs_name, lhs_span)) if lhs_name == "float" => {
// Do the things
}
_ => {}
}
The following code compiles:
fn consume(_: Box<u64>) {}
let tuple = (Some(Box::new(1)), Some(Box::new(2)));
match tuple {
(Some(x), Some(y)) => {
consume(x);
consume(y);
}
_ => (),
}
The following code compiles:
fn consume(_: Box<u64>) {}
match [Some(Box::new(1)), Some(Box::new(2))] {
[Some(x), Some(y)] => {
consume(x);
consume(y);
}
_ => (),
}
But this code does not compile:
fn consume(_: Box<u64>) {}
let array = [Some(Box::new(1)), Some(Box::new(2))];
match array {
[Some(x), Some(y)] => {
consume(x);
consume(y);
}
_ => (),
}
The compiler says:
error[E0382]: use of moved value: `(array[..] as std::prelude::v1::Some).0`
--> src/main.rs:5:24
|
5 | [Some(x), Some(y)] => {
| - ^ value used here after move
| |
| value moved here
|
= note: move occurs because the value has type `std::boxed::Box<u64>`, which does not implement the `Copy` trait
Why do the first and second version compile, but not the third version?
Here's a reduced version of your code:
struct NonCopy;
fn main() {
// OK
let tuple = (Some(NonCopy), Some(NonCopy));
if let (Some(_x), Some(_y)) = tuple {}
// OK
if let [Some(_x), Some(_y)] = [Some(NonCopy), Some(NonCopy)] {}
// Fails
let array = [Some(NonCopy), Some(NonCopy)];
if let [Some(_x), Some(_y)] = array {}
}
The good news
This code works as-is when non-lexical lifetimes are enabled.
The bad news
Non-lexical lifetimes aren't stable yet.
The workaround
Explicitly transfer ownership of the array to the match or if let head expression:
let array = [Some(NonCopy), Some(NonCopy)];
if let [Some(_x), Some(_y)] = { array } {}
The explanation
The current implementation of the borrow checker is AST-based while a future implementation will be MIR-based. At a high level, you can think of this as "working on the code as I typed it" (AST) and "working on the logical flow of data in my code" (MIR).
Certain "hacks" have been added to the AST borrow checker, which is why you can successfully use an array literal but not the variable. With the MIR borrow checker, bigger hacks like these will disappear and the borrow checker will also become more precise, allowing more code to compile.
It is unexpected that the third version does not compile. The same issue occurs when matching boxed values as described here.
Citing the explanation for the error from the linked answer:
My only guess is that the ownership of the Box is moved to the first
param, the param is extracted, then the compiler tries to move it
again to the next parameter.
Replace "Box" with "array" and you get an explanation for what is going on when matching an array. One of the solutions presented in the linked answer also works for matching arrays - the use of curly braces to force a complete move of the Box/array into the match expression:
fn consume(_: Box<u64>) {}
let array = [Some(Box::new(1)), Some(Box::new(2))];
match {array} {
[Some(x), Some(y)] => {
consume(x);
consume(y);
}
_ => (),
}
This question already has answers here:
How do I create a Rust HashMap where the value can be one of multiple types?
(2 answers)
Closed 5 years ago.
I need to pass a HashMap to a function, but this HashMap could theoretically have both integers and strings as values. How do I form the type signature of the function in question for this? I'm new to Rust, and after trying fn do_thing(params: &HashMap<String, _>) and meeting with failure, I ran out of ideas.
fn do_thing(params: &HashMap<String, _>) {
// Irrelevant - regardless, the compiler fails because I cannot use the type placeholder in a function's type sig.
}
fn main() {
let params = HashMap::new();
params.insert(field, value); // field will always be a string, value could be a string or an integer
do_thing(¶ms)
}
This is not a duplicate of the suggested question - constructing a HashMap does not require type declarations, whereas this concerns the HashMap being a function's parameter, which does.
If your value can be int or string, use an enum as follow:
use std::collections::HashMap;
enum Value {
Int(i32),
String(String),
}
fn do_thing(params: &HashMap<String, Value>) {
// each value of params can be Int or String. Check it with a match.
}
Check with a match if Value is Int or String:
match val {
Value::Int(i) => //do something with i
Value::String(s) => //do something with s
}
I need a closure to refer to parts of an object in its enclosing environment. The object is created within the environment and is scoped to it, but once created it could be safely moved to the closure.
The use case is a function that does some preparatory work and returns a closure that will do the rest of the work. The reason for this design are execution constraints: the first part of the work involves allocation, and the remainder must do no allocation. Here is a minimal example:
fn stage_action() -> Box<Fn() -> ()> {
// split a freshly allocated string into pieces
let string = String::from("a:b:c");
let substrings = vec![&string[0..1], &string[2..3], &string[4..5]];
// the returned closure refers to the subtrings vector of
// slices without any further allocation or modification
Box::new(move || {
for sub in substrings.iter() {
println!("{}", sub);
}
})
}
fn main() {
let action = stage_action();
// ...executed some time later:
action();
}
This fails to compile, correctly stating that &string[0..1] and others must not outlive string. But if string were moved into the closure, there would be no problem. Is there a way to force that to happen, or another approach that would allow the closure to refer to parts of an object created just outside of it?
I've also tried creating a struct with the same functionality to make the move fully explicit, but that doesn't compile either. Again, compilation fails with the error that &later[0..1] and others only live until the end of function, but "borrowed value must be valid for the static lifetime".
Even completely avoiding a Box doesn't appear to help - the compiler complains that the object doesn't live long enough.
There's nothing specific to closures here; it's the equivalent of:
fn main() {
let string = String::from("a:b:c");
let substrings = vec![&string[0..1], &string[2..3], &string[4..5]];
let string = string;
}
You are attempting to move the String while there are outstanding borrows. In my example here, it's to another variable; in your example it's to the closure's environment. Either way, you are still moving it.
Additionally, you are trying to move the substrings into the same closure environment as the owning string. That's makes the entire problem equivalent to Why can't I store a value and a reference to that value in the same struct?:
struct Environment<'a> {
string: String,
substrings: Vec<&'a str>,
}
fn thing<'a>() -> Environment<'a> {
let string = String::from("a:b:c");
let substrings = vec![&string[0..1], &string[2..3], &string[4..5]];
Environment {
string: string,
substrings: substrings,
}
}
The object is created within the environment and is scoped to it
I'd disagree; string and substrings are created outside of the closure's environment and moved into it. It's that move that's tripping you up.
once created it could be safely moved to the closure.
In this case that's true, but only because you, the programmer, can guarantee that the address of the string data inside the String will remain constant. You know this for two reasons:
String is internally implemented with a heap allocation, so moving the String doesn't move the string data.
The String will never be mutated, which could cause the string to reallocate, invalidating any references.
The easiest solution for your example is to simply convert the slices to Strings and let the closure own them completely. This may even be a net benefit if that means you can free a large string in favor of a few smaller strings.
Otherwise, you meet the criteria laid out under "There is a special case where the lifetime tracking is overzealous" in Why can't I store a value and a reference to that value in the same struct?, so you can use crates like:
owning_ref
use owning_ref::RcRef; // 0.4.1
use std::rc::Rc;
fn stage_action() -> impl Fn() {
let string = RcRef::new(Rc::new(String::from("a:b:c")));
let substrings = vec![
string.clone().map(|s| &s[0..1]),
string.clone().map(|s| &s[2..3]),
string.clone().map(|s| &s[4..5]),
];
move || {
for sub in &substrings {
println!("{}", &**sub);
}
}
}
fn main() {
let action = stage_action();
action();
}
ouroboros
use ouroboros::self_referencing; // 0.2.3
fn stage_action() -> impl Fn() {
#[self_referencing]
struct Thing {
string: String,
#[borrows(string)]
substrings: Vec<&'this str>,
}
let thing = ThingBuilder {
string: String::from("a:b:c"),
substrings_builder: |s| vec![&s[0..1], &s[2..3], &s[4..5]],
}
.build();
move || {
thing.with_substrings(|substrings| {
for sub in substrings {
println!("{}", sub);
}
})
}
}
fn main() {
let action = stage_action();
action();
}
Note that I'm no expert user of either of these crates, so these examples may not be the best use of it.