How to match against struct fields? [duplicate] - rust

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.

Related

How to match against nested String in Rust

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
}
_ => {}
}

Matching string in Rust [duplicate]

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.

Use of moved value when matching on a value that implements Into<String>

I want a function which accepts both &str and String. The only way I know to do this is by using Into<String>.
However, inside the function, I'm trying to match the Into<String>. This causes some issues, as the arm is a &str.
This is what I have (stripped down):
struct Location {
language: String,
}
fn new<Str>(language: Str) -> Location
where
Str: Into<String>,
{
Location {
language: {
match &*language.into() {
"canada" => "ca".to_owned(),
"uk" => "uk2".to_owned(),
_ => language.into(),
}
},
}
}
fn main() {}
This generates the error:
error[E0382]: use of moved value: `language`
--> src/main.rs:14:22
|
11 | match &*language.into() {
| -------- value moved here
...
14 | _ => language.into(),
| ^^^^^^^^ value used here after move
|
= note: move occurs because `language` has type `Str`, which does not implement the `Copy` trait
My goal is to have allow anything to be passed in, with canada and uk getting swapped out (everything else being left alone).
Right now, it's converting language to a &str for the match operator, which works great. The only problem is I still need the reference to language for the default arm.
How should I go about approaching this? Is there some trivial way to do this I'm missing?
Rust is smart enough to see that your _ => arm of the match statement does not actually borrow the value, so you can do
fn new<Str>(language: Str) -> Location
where
Str: Into<String>,
{
Location {
language: {
let s = language.into();
match &*s {
"canada" => "ca".to_owned(),
"uk" => "uk2".to_owned(),
_ => s,
}
},
}
}
(On the Playground)

Is there a shorter way than a match or if let to get data through many nested levels without increasing the total amount of code?

I work with a bunch of structs / enums included in each other. I need to get ty.node<TyKind::Path>.1.segments.last().identifiers and ty.node<TyKind::Path>.1.segments.last().parameters<AngleBracketed::AngleBracketed>.types.
Is there a simpler way to get these two values then my implementation of f? My ideal syntax would be:
ty.node<TyKind::Path>?.1.segments.last().identifiers
// and
ty.node<TyKind::Path>?.1.segments.last().parameters<AngleBracketed::AngleBracketed>?.types
It that's impossible, maybe there is a way to reduce the number of if let? I want to solve only this particular case, so simplification should be possible compared to f. If an analog of Option::map / Option::unwrap_or_else were introduced, then the sum of its code + the code in f should be less then my original f.
#[derive(Clone)]
struct Ty {
node: TyKind,
}
#[derive(Clone)]
enum TyKind {
Path(Option<i32>, Path),
}
#[derive(Clone)]
struct Path {
segments: Vec<PathSegment>,
}
#[derive(Clone)]
struct PathSegment {
identifier: String,
parameters: Option<Box<PathParameters>>,
}
#[derive(Clone)]
enum PathParameters {
AngleBracketed(AngleBracketedParameterData),
}
#[derive(Clone)]
struct AngleBracketedParameterData {
types: Vec<Box<Ty>>,
}
/// If Tylnode == Path -> return last path segment + types
fn f(ty: &Ty) -> Option<(String, Vec<Box<Ty>>)> {
match ty.node {
TyKind::Path(_, ref path) => if let Some(seg) = path.segments.iter().last() {
let ident = seg.identifier.clone();
println!("next_ty: seg.id {:?}", seg.identifier);
match seg.parameters.as_ref() {
Some(params) => match **params {
PathParameters::AngleBracketed(ref params) => {
Some((ident, params.types.clone()))
}
_ => Some((ident, vec![])),
},
None => Some((ident, vec![])),
}
} else {
None
},
_ => None,
}
}
To simplify the question, I have removed unrelated enum variants and struct fields.
No.
The closest you can get, using nightly features and helper code, is probably this
fn f(ty: &Ty) -> MyOption<(String, Vec<Box<Ty>>)> {
let last = ty.node.path()?.segments.my_last()?;
Just((
last.identifier.clone(),
last.ab_parameters()
.map(|v| v.types.clone())
.unwrap_or_else(|| vec![]),
))
}
Playground
I guess what you want is called Lenses. Not sure about Rust, but here is about Haskell https://en.m.wikibooks.org/wiki/Haskell/Lenses_and_functional_references
It might be possible to implement that in Rust, if somebody haven't done yet.

Pattern matching the tag of a tagged union enum

Is there a way to extract the tag of an enum to, e.g., use it as an index?
I'd like to have a vector of vectors classified by enum type, i.e., if I had:
enum Test {
Point {
x: i32,
y: i32,
},
Point2 {
a: i32,
b: i32,
},
Number(i32),
}
I may want a vector like this:
[[Test::Point{1, 2}, Test::Point{3, 4}], [Test::Number(1), Test::Number(2)]]
I'd like to dynamically add to this vector of vectors given new values. So if a function was passed in a Test::Number, it would go in the second array.
Certainly in the worst case I could explicitly pattern match every possible union value, but for large enums that would get rather verbose and repetitive since all the cases would just end up as
match e {
Test::Point { _, _ } => v[0].push(e),
Test::Point2 { _, _ } => v[1].push(e),
Test::Number(_) => v[2].push(e),
// imagine a lot of these
}
I've tried a lot of syntax permutations, but I haven't gotten anything that will compile. Is there a way to treat the enum struct tags like, well, an enumeration? It looks like there's a FromPrimitive derive, but it's unstable and doesn't work on struct enums.
(I suppose an alternative question if you can't is if you can write a macro to autowrite that match).
You can use .. to ignore all fields of an enum, no matter how many there are, and you can import variants from inside the enums namespace with use. E.g
enum Foo {
X { i: u8 },
Y(u8),
Z
}
fn bar(x: Foo) -> u32 {
use Foo::*;
match x {
X { .. } => 100,
Y(..) => 3,
Z => 12,
}
}
You can use the alternation syntax (via |) in the match arms and methods on the enum to reduce code duplication:
enum Test {
Point1,
Point2,
Point3,
Point4,
Number1,
Number2,
Number3,
Number4,
}
impl Test {
fn bucket(&self) -> u8 {
use Test::*;
match *self {
Point1 | Point2 | Point3 | Point4 => 0,
Number1 | Number2 | Number3 | Number4 => 1,
}
}
}

Resources