How to match on a struct with private fields in Rust? - rust

I have a function which returns an error type that contains private fields. I want the caller to match on this error type, and when matched, print a hardcoded error message. That's because this error type already has a specific meaning, so I'm not interested in the message that err.display() would return.
However, the function could return a different error type in the future. If that happens then I want existing callers to get a compilation error, so that they can update the error message that they print.
However, if callers match on Err(_), then a change in the error type doesn't result in a compilation error.
How do I solve this? One way would be to match on the specific error type, but that doesn't seem possible if the error type has private fields.
Here's example code:
use std::ffi::{CString, NulError};
fn create_cstring() -> Result<CString, NulError> {
return CString::new("hello");
}
fn main() {
match create_cstring() {
Ok(val) => println!("Output: {:?}", val),
Err(_) => println!("Error: input contains forbidden null bytes"),
};
}
Suppose I change create_cstring()'s signature and make it return something other than NulError. How do I make the match block fail to compile?

Use .. to match the private fields:
use std::ffi::{CString, NulError};
fn create_cstring() -> Result<CString, NulError> {
return CString::new("hello");
}
fn main() {
match create_cstring() {
Ok(val) => println!("Output: {:?}", val),
Err(NulError{..}) => println!("Error: input contains forbidden null bytes"),
};
}
Playground

Related

Propagating details of an error upwards in Rust

I would like to have the details of an error be propagated upwards. I used error-chain previously, but that has not been maintained or kept compatible with the rest of the ecosystem as far as i can tell.
For example, in this example:
use std::str::FromStr;
use anyhow::Result;
fn fail() -> Result<u64> {
Ok(u64::from_str("Some String")?)
}
fn main() {
if let Err(e) = fail(){
println!("{:?}", e);
}
The error i am getting is:
invalid digit found in string
I would need the error message to have the key details, including at the point of failure, for example:
- main: invalid digit found in string
- fail: "Some String" is not a valid digit
What's the best way of doing this?
anyhow provides the context() and with_context() methods for that:
use anyhow::{Context, Result};
use std::str::FromStr;
fn fail() -> Result<u64> {
let s = "Some String";
Ok(u64::from_str(s).with_context(|| format!("\"{s}\" is not a valid digit"))?)
}
fn main() {
if let Err(e) = fail() {
println!("{:?}", e);
}
}
"Some String" is not a valid digit
Caused by:
invalid digit found in string
If you want custom formatting, you can use the Error::chain() method:
if let Err(e) = fail() {
for err in e.chain() {
println!("{err}");
}
}
"Some String" is not a valid digit
invalid digit found in string
And if you want additional details (e.g. where the error happened), you can use a custom error type and downcast it (for error source you can also capture a backtrace).
This is a tricky thing to accomplish and I'm not sure that there is a simple and non-invasive way to capture all of the details of any possible error without knowledge of the particular function being invoked. For example, we may want to display some arguments to the function call that failed, but evaluating other arguments might be problematic -- they may not even be able to be turned into strings.
Maybe the argument is another function call, too, so should we capture its arguments or only its return value?
I whipped up this example quickly to show that we can at least fairly trivially capture the exact source expression. It provides a detail_error! macro that takes an expression that produces Result<T, E> and emits an expression that procudes Result<T, DetailError<E>>. The DetailError wraps the original error value and additionally contains a reference to a string of the original source code fed to the macro.
use std::error::Error;
use std::str::FromStr;
#[derive(Debug)]
struct DetailError<T: Error> {
expr: &'static str,
cause: T,
}
impl<T: Error> DetailError<T> {
pub fn new(expr: &'static str, cause: T) -> DetailError<T> {
DetailError { expr, cause }
}
// Some getters we don't use in this example, but should be present to have
// a complete API.
#[allow(dead_code)]
pub fn cause(&self) -> &T {
&self.cause
}
#[allow(dead_code)]
pub fn expr(&self) -> &'static str {
self.expr
}
}
impl<T: Error> Error for DetailError<T> { }
impl<T: Error> std::fmt::Display for DetailError<T> {
fn fmt(&self, f: &mut std::fmt::Formatter) -> Result<(), std::fmt::Error> {
write!(f, "While evaluating ({}): ", self.expr)?;
std::fmt::Display::fmt(&self.cause, f)
}
}
macro_rules! detail_error {
($e:expr) => {
($e).map_err(|err| DetailError::new(stringify!($e), err))
}
}
fn main() {
match detail_error!(u64::from_str("Some String")) {
Ok(_) => {},
Err(e) => { println!("{}", e); }
};
}
This produces the runtime output:
While evaluating (u64::from_str("Some String")): invalid digit found in string
Note that this only shows the string because it's a literal in the source. If you pass a variable/parameter instead, you will see that identifier in the error message instead of the string.
When you run your app with the environment variable RUST_BACKTRACE set to 1 or full, you'll get more error details, without the need to recompile your program. That, however, doesn't mean you're going to get an extra message like "Some String" is not a valid digit, as the parsing function simply doesn't generate such.

Matching on a enum String

I am learning Rust and I am trying to write a simple IP handling function
enum IpAddr{
v4(u8,u8,u8,u8),
v6(String),
}
impl IpAddr{
fn write(&self){
match *self {
IpAddr::v4(A,B,C,D) => println!("{}.{}.{}.{}",A,B,C,D),
IpAddr::v6(S) => println!("{}",S)
}
}
}
The v4 matches fine, but I get the following build error on the 2nd one
error[E0507]: cannot move out of self.0 which is behind a shared reference
move occurs because _S has type String, which does not implement the Copy trait
How can I match on a enum with a String attached?
Its complaining because you are trying to copy self by dereferencing, but IpAddr contains a String which is not copy-able.
Remove the dereference and it should work as expected
match self {
IpAddr::v4(A,B,C,D) => println!("{}.{}.{}.{}",A,B,C,D),
IpAddr::v6(S) => println!("{}",S)
}
I'm not sure why you're matching on *self, as it does not appear to be necessary, but if you really need to do that for some reason, you can also solve the problem by using the ref keyword with your variable (ref s), which will cause the value to be borrowed instead of moving it.
enum IpAddr{
V4(u8,u8,u8,u8),
V6(String),
}
impl IpAddr{
fn write(&self){
match *self {
IpAddr::V4(a,b,c,d) => println!("{}.{}.{}.{}",a,b,c,d),
IpAddr::V6(ref s) => println!("{}", s)
}
}
}
Playground

What is unwrap in Rust, and what is it used for?

I have this code that uses .unwrap():
fn main() {
let paths = std::fs::read_dir("/home/user").unwrap();
for path in paths {
println!("Name: {}", path.unwrap().path().display());
}
}
After looking at the definition of unwrap,
pub fn unwrap(self) -> T {
match self {
Ok(t) => t,
Err(e) => unwrap_failed("called `Result::unwrap()` on an `Err` value", e),
}
}
And the signature of read_dir
pub fn read_dir<P: AsRef<Path>>(path: P) -> io::Result<ReadDir>
Am I correct in understanding that unwrap returns the T type that is passed in Result?
In Rust, when you have an operation that may either return a T or fail, you will have a value of type Result<T,E> or Option<T> (E will be the error condition in case of an interesting error).
The function unwrap(self) -> T will give you the embedded T if there is one. If instead there is not a T but an E or None then it will panic.
It is best used when you are positively sure that you don't have an error. If that is not the case usually it is better either pattern-match the error or use the try! macro ? operator to forward the error.
In your example, the call to read_dir() returns a io::Result<ReadDir> because opening the directory might fail. And iterating the opened directory returns multiple values of type io::Result<DirEntry> because reading the directory might also fail.
With try! ? it would be something like this:
fn try_main() -> std::io::Result<()> {
let entries = std::fs::read_dir("/home/user")?;
for entry in entries {
println!("Name: {}", entry?.path().display());
}
Ok(())
}
fn main() {
let res = try_main();
if let Err(e) = res {
println!("Error: {}", e);
}
}
Look how every error case is checked.
(Updated to use ? instead of try!(). The macro still works, but the ? is preferred for new code).
The problem is that reading a line from a file produces a potential error type. The type is
Result<String,std::io::Error>
Result is an enum. There are two potential values in the Result, they are used for error handling and management. The first value is Err. If Err is populated, there was an error in the function that was called. The other potential selection is Ok. Ok contains a value.
enum Result<T, E> {
Ok(T),
Err(E),
}
Enum is a complex type, rather than a single value. To get the value we are looking for, we use unwrap() to unwrap the value.
unwrap() is used here to handle the errors quickly. It can be used on any function that returns Result or Option (Option is also enum). If the function returns an Ok(value), you will get the value. If the function returns an Err(error), the program will panic.

What can be done with Rust's generic FromStr object?

Rust's str class has a parse method that returns a FromStr object. parse is templated, and so the type that's being parsed from the str can be manually specified, e.g. "3".parse::<i32>() evaluates to (a Result object containing) the 32-bit int 3.
But failing to specify the type does not seem to be an error in itself. Instead, I get an error when trying to print the resulting (generic/unspecified) FromStr object:
let foo = "3".parse();
match foo
{
Ok(m) => println!("foo: {}", m),
Err(e) => println!("error! {}", e)
}
This does not give an error on the first line; instead, I get the following error:
<anon>:24:12: 24:13 error: unable to infer enough type information about `_`; type annotations or generic parameter binding required [E0282]
<anon>:24 Ok(m) => println!("foo: {}", m),
(Here, line 24 is the line with the Ok(m).)
So what is m here? Or is the "unable to infer enough type information" error actually due to the fact that parse in fact can't be called without a type specifier, and the compiler just doesn't catch the error until the first line where the resulting Ok type is actually used?
Rust's str class has a parse method that returns a FromStr object.
Stop right here, this is your error.
parse does not return a FromStr object; FromStr is a trait which can be thought of as an abstract class if you come from an OO background, and you cannot return an object with an abstract type: it's abstract!
What parse does return, thus, is an instance of some type T which must implement the FromStr interface.
But failing to specify the type does not seem to be an error in itself. Instead, I get an error when trying to print the resulting (generic/unspecified) FromStr object
Because there cannot be such generic/unspecific FromStr object. A concrete type must be inferred (from context) or explicitly spelled out, and this type must implement FromStr.
So what is m here?
Only you know what it should be, the compiler does not, and thus complain that it does not know what to do :)
Or is the "unable to infer enough type information" error actually due to the fact that parse in fact can't be called without a type specifier, and the compiler just doesn't catch the error until the first line where the resulting Ok type is actually used?
Basically.
Except that it's not so much that the compiler doesn't catch the error until the first line where the resulting Ok is used, and more that the compiler considers the full function at once when inferring types. From the point of view of the compiler, whether the actual clue to infer the type comes immediately or comes 50 lines down does not matter, it only needs to be present in the current function body.
It might lead to the complaint about the lack of type originating in an odd place from the developer point of view; this is one of the downfalls of type inference. On the other hand, the compiler just cannot know where YOU would prefer to put the annotation. There are after all many possibilities:
// Example 1: immediately specifying the type
fn main() {
let foo = "3".parse::<i32>();
match foo
{
Ok(m) => println!("foo: {}", m),
Err(e) => println!("error! {}", e)
}
}
// Example 2: partially specifying the result type
// Note: the "_" is deduced to be std::num::ParseIntError because
// this is how `FromStr::Err` is defined for `i32`.
fn main() {
let foo: Result<i32, _> = "3".parse();
match foo
{
Ok(m) => println!("foo: {}", m),
Err(e) => println!("error! {}", e)
}
}
// Example 3: specifying the result type of unwrapping
fn doit() -> Result<(), std::num::ParseIntError> {
let foo: i32 = try!("3".parse());
println!("foo: {}", foo);
Ok(())
}
fn main() {
match doit()
{
Ok(_) => (),
Err(e) => println!("error! {}", e)
}
}
// Example 4: letting the type be inferred from a function call
fn callit(f: i32) {
println!("f: {}", f);
}
fn main() {
let foo = "3".parse();
match foo
{
Ok(m) => callit(m),
Err(e) => println!("error! {}", e)
}
}
It's just not clear what m is here, as there isn't enough information to say. Is it an i32? A u64? Nobody, including Rust, can know.
You need to do something to help figure out what type it is. Either pass it to a function expecting a specific type, or annotate it such that it can be determined what type it should be.

What happens in Rust using "match" if nothing matches?

I'm learning Rust and it looks very interesting. I'm not yet very familiar with "match", but it looks to be fairly integral. I was using the following code (below) to convert String to i64 which included the commented-out line "None" in place of the next line "_". I wondered what happened in the event of a non-match without underscore or whether "None" may be a catch-all. The calling-code requires a positive i64, so negative results in an invalid input (in this case). I'm not sure if it's possible to indicate an error in a return using this example other than perhaps using a struct.
Without underscore as a match item, will "None" catch all, and can it be used instead of underscore?
Is it possible to return an error in a function like this without using a struct as the return value?
In general, is it possible for a non-match using "match" and if so, what happens?
In the example below, is there any difference between using underscore and using "None"?
Example code :
fn fParseI64(sVal: &str) -> i64 {
match from_str::<i64>(sVal) {
Some(iVal) => iVal,
// None => -1
_ => -1
}
}
Without underscore as a match item, will "None" catch all, and can it be used instead of underscore?
In this case, yes. There are only 2 cases for Option<T>: Some<T> or None.
You are already matching the Some case for all values, None is the only case that's left, so
None will behave exactly the same as _.
2. Is it possible to return an error in a function like this without using a struct as the return value?
I'd suggest you read this detailed guide to understand all the possible ways to handle errors -
http://static.rust-lang.org/doc/master/tutorial-conditions.html
Using Options and Results will require you to return a struct.
3. In general, is it possible for a non-match using "match" and if so, what happens?
Rust won't compile a match if it can't guarantee that all the possible cases are handled. You'll need to have a _ clause if you are not matching all possibilities.
4. In the example below, is there any difference between using underscore and using "None"?
No. See Answer #1.
Here are 3 of the many ways you can write your function:
// Returns -1 on wrong input.
fn alt_1(s: &str) -> i64 {
match from_str::<i64>(s) {
Some(i) => if i >= 0 {
i
} else {
-1
},
None => -1
}
}
// Returns None on invalid input.
fn alt_2(s: &str) -> Option<i64> {
match from_str::<i64>(s) {
Some(i) => if i >= 0 {
Some(i)
} else {
None
},
None => None
}
}
// Returns a nice message describing the problem with the input, if any.
fn alt_3(s: &str) -> Result<i64, ~str> {
match from_str::<i64>(s) {
Some(i) => if i >= 0 {
Ok(i)
} else {
Err(~"It's less than 0!")
},
None => Err(~"It's not even a valid integer!")
}
}
fn main() {
println!("{:?}", alt_1("123"));
println!("{:?}", alt_1("-123"));
println!("{:?}", alt_1("abc"));
println!("{:?}", alt_2("123"));
println!("{:?}", alt_2("-123"));
println!("{:?}", alt_2("abc"));
println!("{:?}", alt_3("123"));
println!("{:?}", alt_3("-123"));
println!("{:?}", alt_3("abc"));
}
Output:
123i64
-1i64
-1i64
Some(123i64)
None
None
Ok(123i64)
Err(~"It's less than 0!")
Err(~"It's not even a valid integer!")

Resources