Two small discoveries about Rust

Posted by snake310 on Sat, 01 Jan 2022 02:17:38 +0100

Two small discoveries about Rust in the process of writing code may be my discovery, but they are already familiar to everyone, but we still need to record:

How to handle error in iter tool function

Suppose there is an integer array l, we need to traverse the array, multiply each element by 2, and then collect the results. However, if the array contains negative numbers, an error is returned.

When I used to write code with similar functions, I usually wrote this:

fn for_loop_double(l: Vec<i32>) -> Result<Vec<i32>, MyError> {
    let mut res = Vec::new();
    for v in l {
        if v < 0 {
            return Err(MyError::NegativeError);
        }
        res.push(v * 2);
    }
    Ok(res)
}

Why don't you use ITER () map()? I also want to use it, but the one passed to map () is a closure. You can't control the caller function in the closure to return directly. In fact, I always think Rust should not be so weak. If there is no good solution to this problem, it is really ashamed of the name of the most popular language. So there is today's discovery. Post the code first

fn iter_double(l: Vec<i32>) -> Result<Vec<i32>, MyError> {
    l.into_iter()
        .map(|v| {
            if v < 0 {
                return Err(MyError::NegativeError);
            }
            Ok(v * 2)
        })
        .collect()
}

I always think that the result obtained by using collect() as above should be VEC < result < I32, myerror > >. Today, I really can't stand all kinds of for (by the way, this is also the reason why I defected from go to t rust) and search for solutions online. The result was not unexpected. I also gave a brief explanation( Original link)

#[derive(Debug)]
struct Item;
type Id = String;

fn find(id: &Id) -> Result<Item, String> {
    Err(format!("Not found: {:?}", id))
}

fn main() {
    let s = |s: &str| s.to_string();
    let ids = vec![s("1"), s("2"), s("3")];

    let items: Result<Vec<_>, _> = ids.iter().map(find).collect();
    println!("Result: {:?}", items);
}

Result implements FromIterator, so you can move the Result outside and iterators will take care of the rest (including stopping iteration if an error is found).

The original trust iterator also has the function of automatically terminating in case of error. But what if I don't want to encounter error termination and want to put error in the result? It really makes me feel that the moment of trust's most popular award is coming. Look at the code first:

fn iter_err_double(l: Vec<i32>) -> Vec<Result<i32, MyError>> {
    l.into_iter()
        .map(|v| {
            if v < 0 {
                return Err(MyError::NegativeError);
            }
            Ok(v * 2)
        })
        .collect()
}

It's like iter_double() is exactly the same. The only difference is that the return value is changed from result < VEC < I32 >, myerror > to VEC < result < I32 >, myerror >.

Test:

    fn it_works() {
        assert_eq!(
            iter_err_double(vec![1, 2, -1, 4]),
            vec![Ok(2), Ok(4), Err(MyError::NegativeError), Ok(8)]
        );
    }

Passed
I'm shallow
From now on, whether go adds generics or not, t rust will definitely stay in my toolkit.

About variable initialization

As we all know, there are no null s and uninitialized default values in trust. In short, if you say there is a cup in this place, there must be a cup in this place. The size and color of the cup must be confirmed on the spot. No cup is still on the road. Let's occupy this place first and bring it here later. But when I was writing today, I inadvertently tried not to initialize, but I succeeded (of course, this is a superficial phenomenon. In fact, I still need to initialize. Please don't see that this directly closes the browser and goes out to preach the wrong theory. In trust (except for the unsafe part), there are no uninitialized variables, which is beyond doubt).

fn chain_iter(l1: Option<Vec<i32>>, l2: Option<Vec<i32>>) -> Vec<i32> {
	// Declaration, but no assignment, can pass the test
    let iter: Box<dyn Iterator<Item = i32>>;
    if let Some(l1) = l1 {
        if let Some(l2) = l2 {
            iter = Box::new(l1.into_iter().chain(l2));
        } else {
            iter = Box::new(l1.into_iter());
        }
    } else {
        if let Some(l2) = l2 {
            iter = Box::new(l2.into_iter());
        } else {
            return vec![];
        }
    }
    iter.collect()
}

In fact, I have seen a lot in other people's code before, but I haven't used it very much. In fact, iter must also be initialized, but the t rust compiler saw that I honestly initialized it in the subsequent code, so it let me go. If we make a slight change to the code, the compiler will immediately report an error

fn chain_iter(l1: Option<Vec<i32>>, l2: Option<Vec<i32>>) -> Vec<i32> {
    let iter: Box<dyn Iterator<Item = i32>>;
    if let Some(l1) = l1 {
        if let Some(l2) = l2 {
            iter = Box::new(l1.into_iter().chain(l2));
        } else {
            iter = Box::new(l1.into_iter());
        }
    } else {
        if let Some(l2) = l2 {
            iter = Box::new(l2.into_iter());
        }
    }
    iter.collect()
}

Errors are reported as follows:

use of possibly-uninitialized variable: iter
use of possibly-uninitialized iterrustcE0381

Tell you that you used a variable that may not be initialized

The initialization of variables can be done only before the variables are used. But don't be too far apart, otherwise it will be a big burden for later code maintenance. The key is that the person who maintains the code is likely to be yourself.

OK, I'm tired of writing. In general, I like trust more and more. The feeling of being cheated into a stolen ship some time ago is disappearing a little. I don't know if those peers who voted for trust also pressed the voting button after this experience.

Topics: Rust