Doing this exercise https://github.com/rust-lang/rustlings/blob/main/exercises/option/option3.rs I discovered the ref keyword and I'm very confused when should I use & or ref in a pattern matching. In this example, every match outputs the same message, but I couldn't tell their differences:
struct Point { x: i32, y: i32}
fn main() {
let y: Option<Point> = Some(Point { x: 100, y: 200 });
match &y {
Some(p) => println!("Co-ordinates are {},{} ", p.x, p.y),
_ => println!("no match"),
}
match y {
Some(ref p) => println!("Co-ordinates are {},{} ", p.x, p.y),
_ => println!("no match"),
}
match &y {
Some(ref p) => println!("Co-ordinates are {},{} ", p.x, p.y),
_ => println!("no match"),
}
y;
}
ref is older. The fact the matching against a reference gives a reference is a result of the match ergonomics feature.
They are not exactly the same, but generally you can choose. I and many other people prefer the & form, although I saw people that do not like match ergonomics and prefer the explicit ref always.
There are case where you cannot take a reference and then you're forced to use ref. I also prefer to use ref in cases like Option::as_ref() and Option::as_mut(), where with match ergonomics they will have exactly the same code and this is confusing in my opinion, while using ref one is just ref and the other uses ref mut.
Related
Can Rust match struct fields? For example, this code:
struct Point {
x: bool,
y: bool,
}
let point = Point { x: false, y: true };
match point {
point.x => println!("x is true"),
point.y => println!("y is true"),
}
Should result in:
y is true
Can Rust match struct fields?
It is described in the Rust book in the "Destructuring structs" chapter.
match point {
Point { x: true, .. } => println!("x is true"),
Point { y: true, .. } => println!("y is true"),
_ => println!("something else"),
}
The syntax presented in your question doesn't make any sense; it seems that you just want to use a normal if statement:
if point.x { println!("x is true") }
if point.y { println!("y is true") }
I'd highly recommend re-reading The Rust Programming Language, specifically the chapters on
enums
match
patterns
Once you've read that, it should become clear that point.x isn't a pattern, so it cannot be used on the left side of a match arm.
I get little confused when learning about control flow. I don't understand the difference between if-let and match.
fn main() {
let some_u8_value = Some(8u8);
// println!(" {} ", some_u8_value);
if let Some(value) = some_u8_value {
println!(" {} ", value);
} else {
println!("not a num");
}
match some_u8_value {
Some(value) => println!(" {} ", value),
None => println!("not a num"),
}
}
Why do we need if-let?
Why do we need if-let?
We don't need it, it's a convenience feature. Per RFC 160 which introduced it:
[if let] allows for refutable pattern matching without the syntactic and semantic overhead of a full match, and without the corresponding extra rightward drift.
and
The idiomatic solution today for testing and unwrapping an Option looks like
match optVal {
Some(x) => {
doSomethingWith(x);
}
None => {}
}
This is unnecessarily verbose, with the None => {} (or _ => {}) case being required, and introduces unnecessary rightward drift (this introduces two levels of indentation where a normal conditional would introduce one).
[explanation of the issues with using a simple if with is_some and unwrap]
The if let construct solves all of these problems, and looks like this:
if let Some(x) = optVal {
doSomethingWith(x);
}
Short of using the feature #![feature(move_ref_pattern)], what are the strategies for handling a match with both a move and reference in the pattern?
Often, I have a routine that passes in an argument by reference and an argument by move. I'd like to match on a tuple between them because it makes it easier for me to see the different combinations. At the same time, I'd like to appropriately use the memory from the second argument since it's already owned by the routine.
At the moment, the way I handle it is by matching on a mutable reference and then calling std::mem::replace to get access to the value. This feels clunky to me and I'd like to see if there's a better way to handle this.
The struct F64 is used to prevent Copy from being called in this mostly contrived example:
// Cloneable, but not copyable float type
#[derive(Clone, Debug)]
enum MyFloat {
Zero,
Num(F64),
}
#[derive(Clone, Debug)]
struct F64(f64);
// Consume y, but not x
fn my_add(x: &MyFloat, mut y: MyFloat) -> MyFloat {
match (x, &mut y) {
(MyFloat::Zero, MyFloat::Zero) => MyFloat::Zero,
(MyFloat::Zero, y # MyFloat::Num(_)) => std::mem::replace(y, MyFloat::Zero),
(MyFloat::Num(_), MyFloat::Zero) => x.clone(),
(MyFloat::Num(x), MyFloat::Num(y)) => {
let y = std::mem::replace(y, F64(0.));
MyFloat::Num(my_add_driver(x, y))
}
}
}
fn my_add_driver(x: &F64, y: F64) -> F64 {
F64((*x).0 + y.0)
}
// Run the program
fn main() {
let x = MyFloat::Num(F64(1.2));
let y = MyFloat::Num(F64(2.3));
println!("{:?} + {:?} = {:?}", x, y.clone(), my_add(&x, y));
}
Here, y is passed into the match as a mutable reference, which solves the move-ref issue with the pattern match. However, we have to then extract its memory using std::mem::replace. Is there a better way to handle this?
You can simplify it slightly like this:
fn my_add(x: &MyFloat, y: MyFloat) -> MyFloat {
// match on y by value, not by ref:
match (x, y) {
(MyFloat::Zero, MyFloat::Zero) => MyFloat::Zero,
(MyFloat::Zero, y # MyFloat::Num(_)) => y,
(MyFloat::Num(_), MyFloat::Zero) => x.clone(),
(MyFloat::Num(x), MyFloat::Num(ref mut y)) => {
let y = std::mem::replace(y, F64(0.));
MyFloat::Num(my_add_driver(x, y))
}
}
}
To get rid of the other mem::replace() call is only possible with the move_ref_pattern feature, or with a helper method similar to Option::as_ref():
impl MyFloat {
fn as_ref_option(&self) -> Option<&F64> {
match self {
MyFloat::Zero => None,
MyFloat::Num(f) => Some(f),
}
}
}
// Consume y, but not x
fn my_add(x: &MyFloat, y: MyFloat) -> MyFloat {
match (x.as_ref_option(), y) {
(None, MyFloat::Zero) => MyFloat::Zero,
(None, y # MyFloat::Num(_)) => y,
(Some(_), MyFloat::Zero) => x.clone(),
(Some(x), MyFloat::Num(y)) => MyFloat::Num(my_add_driver(x, y)),
}
}
Playground link
I am confused about the Some(T) keyword.
I want to check for two variables, if the value is defined (not None). If that is the case, the value of this variables is processed.
I know the match pattern which works like this:
match value {
Some(val) => println!("{}", val),
None => return false,
}
If I use this pattern, it will get very messy:
match param {
Some(par) => {
match value {
Some(val) => {
//process
},
None => return false,
}
},
None => return false,
}
This can't be the right solution.
The is a possibility, to ask if the param and value is_some() That would effect code like that:
if param.is_some() && value.is_some() {
//process
}
But if I do it like that, I always have to unwrap param and value to access the values.
I thought about something like this to avoid that. But this code does not work:
if param == Some(par) && value == Some(val) {
//process
}
The idea is that the values are accessible by par and val like they are in the match version.
Is there any solution to do something like this?
If I have several Option values to match, I match on a tuple of the values:
enum Color {
Red,
Blue,
Green,
}
fn foo(a: Option<Color>, b: Option<i32>) {
match (a, b) {
(Some(Color::Blue), Some(n)) if n > 10 => println!("Blue large number"),
(Some(Color::Red), _) => println!("Red number"),
_ => (),
}
}
fn main() {
foo(Some(Color::Blue), None);
foo(Some(Color::Blue), Some(20));
}
This allows me to match the combinations that are interesting, and discard the rest (or return false, if that is what you want to do).
If your function is processing multiple Option values, and would like to discard them if they're not Some, your function could return an Option itself:
fn foo(param: Option<usize>, value: Option<usize>) -> Option<usize> {
let result = param? + value?;
Some(result)
}
This will short-circuit the function in case there's a None value stored in either param or value.
Please read the book for more information on the ? operator.
If your function can't return an Option, you can still get away with destructuring using if let or match:
let x = if let (Some(p), Some(v)) = (param, value) {
p + v
} else {
return 0;
}
let x = match (param, value) {
(Some(p), Some(v)) => p + v,
(Some(p), _) => p,
(_, Some(v) => v,
_ => return 0,
}
Please read What is this question mark operator about? for more information on the ? operator
Please read this chapter in Rust by Example for more information on destructuring multiple things at once
There's a couple more alternatives not yet listed:
If you're willing to use experimental features (and hence the nightly compiler) you can use a try block as an alternative of extracting a function.
#![feature(try_blocks)]
fn main() {
let par: Option<f32> = Some(1.0f32);
let value: Option<f32> = Some(2.0f32);
let x: Option<f32> = try { par? + value? };
println!("{:?}", x);
}
Another alternative is to use map which only applies if the value is not None
let x: Option<f32> = par.map(|p| value.map(|v| p + v));
I have code using a nested Result like this:
fn ip4(s: &str) -> Result<(u8, u8, u8, u8), num::ParseIntError> {
let t: Vec<_> = s.split('.').collect();
match t[0].parse::<u8>() {
Ok(a1) => {
match t[1].parse::<u8>() {
Ok(a2) => {
match t[2].parse::<u8>() {
Ok(a3) => {
match t[3].parse::<u8>() {
Ok(a4) => {
Ok((a1, a2, a3, a4))
}
Err(er) => Err(er)
}
},
Err(er) => Err(er)
}
}
Err(er) => Err(er)
}
}
Err(er) => Err(er),
}
}
Is there any function or composing way to reduce this? Something like Haskell or Scala programmers do:
fn ip4(s: &str) -> Result<(u8, u8, u8, u8), num::ParseIntError> {
let t: Vec<_> = s.split('.').collect();
Result
.lift((,,,))
.ap(() -> t[0].parse::<u8>())
.ap(() -> t[1].parse::<u8>())
.ap(() -> t[2].parse::<u8>())
.ap(() -> t[3].parse::<u8>()) // maybe more concise in Haskell or Scala but I think it's enough :)
}
The answer to your direct question is the questionmark operator which would allow you to replace your whole match block with
Ok((
t[0].parse::<u8>()?,
t[1].parse::<u8>()?,
t[2].parse::<u8>()?,
t[3].parse::<u8>()?,
))
where essentially ? will return the error immediately if one is encountered.
That said, Rust already provides APIs for parsing IP addresses. Even if you wanted to maintain your tuple approach (though why would you), you could implement your function as
fn ip4(s: &str) -> Result<(u8, u8, u8, u8), net::AddrParseError> {
let addr: net::Ipv4Addr = s.parse()?;
let octets = addr.octets();
Ok((octets[0], octets[1], octets[2], octets[3]))
}
or just pass around the Ipv4Addr value directly.
Though, I do not see anything bad in #loganfsmyth's answer, I want to add another solution.
Your problem is a very simple and general problem of all programming languages which can be solved very easily if you would have enough time or practice in optimizing solutions. There is some divide and conquer recursive technique which is usually used to solve such problems. For a start, imagine a more simple thing: parsing a single octet from a string. This is a simple parse which you already know. Then mentally try to expand this problem to a larger one - parsing all octets which is a simple repeating process of the smallest problem we have solved earlier (parsing a single octet). This leads us to an iterative/recursive process: do something until. Keeping this in mind I have rewritten your function to a simple iterative process which uses tail-recursion which will not cause a stack overflow as a usual recursion due to it's form:
use std::num;
#[derive(Debug, Copy, Clone)]
struct IpAddressOctets(pub u8, pub u8, pub u8, pub u8);
type Result = std::result::Result<IpAddressOctets, num::ParseIntError>;
fn ipv4(s: &str) -> Result {
let octets_str_array: Vec<_> = s.split('.').collect();
// If it does not contain 4 octets then there is a error.
if octets_str_array.len() != 4 {
return Ok(IpAddressOctets(0, 0, 0, 0)) // or other error type
}
let octets = Vec::new();
fn iter_parse(octets_str_array: Vec<&str>, mut octets: Vec<u8>) -> Result {
if octets.len() == 4 {
return Ok(IpAddressOctets(octets[0], octets[1], octets[2], octets[3]))
}
let index = octets.len();
octets.push(octets_str_array[index].parse::<u8>()?);
iter_parse(octets_str_array, octets)
}
iter_parse(octets_str_array, octets)
}
fn main() {
println!("IP address octets parsed: {:#?}", ipv4("10.0.5.234"));
}
Keep in mind that Rust language is a bit more functional than you might think.
Also, I would recommend you to read this book which greatly explains the solution.
You can use early returns to prevent the nesting (but not the repetition).
Note the body of the Err arms of the matches:
fn ip4(s: &str) -> Result<(u8, u8, u8, u8), num::ParseIntError> {
let t: Vec<_> = s.split('.').collect();
let a1 = match t[0].parse::<u8>() {
Ok(x) => x,
Err(er) => return Err(er),
};
let a2 = match t[1].parse::<u8>() {
Ok(x) => x,
Err(er) => return Err(er),
};
let a3 = match t[2].parse::<u8>() {
Ok(x) => x,
Err(er) => return Err(er),
};
let a4 = match t[3].parse::<u8>() {
Ok(x) => x,
Err(er) => return Err(er),
};
(a1, a2, a3, a4)
}
But, as the others have said, Rust already has a built-in way to parse IP addresses.