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
}
_ => {}
}
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.
I'm writing a parser in Rust, which needs at various points to match the current token against candidate values. Some of the candidate values are characters, others are integer constants, so the token is declared as i32, which would be plenty to accommodate both. (All the characters to be matched against are ASCII.)
The problem is that when I supply a character constant like '(' to be matched against, the compiler complains that it expected i32 and is getting char.
I tried writing e.g. '(' as i32 but an as expression is not allowed as a match candidate.
Obviously I could look up the ASCII values and provide them as numbers, but it seems there should be a more readable solution. Declaring the token as char doesn't really seem correct, as it sometimes needs to hold integers that are not actually characters.
What's the recommended way to solve this problem?
It’s a bit verbose, but your match arms could be of the form c if c == i32::from(b'(').
Another alternative would be to match on u8::try_from(some_i32) (branch arms Some(b'(') and then either None if some_i32 == … or None => { match some_i32 { … } }).
Yet another would be to change the type from i32 to your own enum, which is probably the cleanest option but might require some convincing of the Rust compiler to get an i32-like representation if you need that for some reason.
Finally, you could define const PAREN_OPEN: i32 = b'(' as i32; and use PAREN_OPEN as the pattern.
Since as expressions are allowed in constants, and matching is allowed against constants, you can use a constant:
const LPAREN: i32 = '(' as i32;
match v {
LPAREN => { ... }
// ...
}
If you can use nightly, you can use the inline_const_pat feature to reduce the boilerplate:
#![feature(inline_const_pat)]
match v {
const { '(' as i32 } => { ... }
// ...
}
Another way: here's a small proc macro that will replace the characters with their numerical value (it does not work with nested char patterns):
use proc_macro::TokenStream;
use quote::ToTokens;
#[proc_macro]
pub fn i32_match(input: TokenStream) -> TokenStream {
let mut input = syn::parse_macro_input!(input as syn::ExprMatch);
for arm in &mut input.arms {
if let syn::Pat::Lit(lit) = &mut arm.pat {
if let syn::Expr::Lit(syn::ExprLit { lit, .. }) = &mut *lit.expr {
if let syn::Lit::Char(ch) = lit {
*lit = syn::Lit::Int(syn::LitInt::new(
&(ch.value() as i32).to_string(),
ch.span(),
));
}
}
}
}
input.into_token_stream().into()
}
i32_match! {
match v {
'(' => { ... }
// ...
}
}
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.
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.
I have a Rust enum defined like this
enum MyFirstEnum {
TupleType(f32, i8, String),
StuctType {varone: i32, vartwo: f64},
NewTypeTuple(i32),
SomeVarName
}
I have the following code:
let mfe: MyFirstEnum = MyFirstEnum::TupleType(3.14, 1, "Hello".to_string());
I'm following the Rust documentation and this looks fine. I don't need to define everything in the enum, but how would I go about accessing the mid element in the enum tuple?
mfe.TupleType.1 and mfe.1 don't work when I add them to a println!
I know Rust provides the facility to do pattern matching to obtain the value, but if I changed the code to define the other variants within the enum, the code to output a particular variant would quickly become a mess.
Is there a simple way to output the variant of the tuple (or any other variant) in the enum?
This is a common misconception: enum variants are not their own types (at least in Rust 1.9). Therefore when you create a variable like this:
let value = MyFirstEnum::TupleType(3.14, 1, "Hello".to_string());
The fact that it's a specific variant is immediately "lost". You will need to pattern match to prevent accessing the enum as the wrong variant. You may prefer to use an if let statement instead of a match:
if let MyFirstEnum::TupleType(f, i, s) = value {
// Values available here
println!("f: {:?}", f);
}
Example solution:
enum MyFirstEnum {
TupleType(f32, i8, String),
// StuctType { varone: i32, vartwo: f64 },
// NewTypeTuple(i32),
// SomeVarName,
}
fn main() {
let mfe: MyFirstEnum = MyFirstEnum::TupleType(3.14, 1, "Hello".to_string());
let MyFirstEnum::TupleType(value, id, text) = &mfe;
println!("[{}; {}; {}]", value, id, text);
//or
match &mfe {
MyFirstEnum::TupleType(value, id, text) => {
println!("[{}; {}; {}]", value, id, text);
}
// _ => {}
}
}
Playground link