Infinite initializations

Good day folks,
I am still struggling with error handling. In this case I want to open a registry key if it exists. If not, then create it. I need to return a RegKey from the match;Err arm, but create() returns a Return, which means a second match to satisfy the first match, which means a third match to satisfy the second, ad infinitum. Clearly i am missing a key point. Any instruction would be appreciated.
Thanks all

use registry::Hive;
use registry::Security;
use registry::RegKey;

fn main() {
    let hive:Hive;
    let r_key: RegKey;
    let regkey = match Hive::open(&hive, r"CurrentUser\Software\Backoffice", Security::AllAccess) {
        Ok(key) => key,
        Err(_) => {
           let r_key = Hive::create(&hive, r"CurrentUser\Software\Backoffice", Security::AllAccess);
            r_key
        },    
    };
}

What do you want to happen if the create call fails?

There are many alternatives to match statements. For example, there is the question mark operator, which you can read more about in the book. There are also various methods on Result such as or_else, which you might find useful. Finally there is of course unwrap, which aborts the program if the operation failed.

AHA! moment. I was wondering if propagating the error up the chain with ? would solve the problem but I think I just got it. Am I correct that ? propagates only the error, not the Result<T, E>?

It depends on what you mean. The function itself must return a Result to use the question mark operator. Perhaps it is useful to post the expanded version of expression?.

match expression {
    Ok(value) => value,
    Err(err) => return Err(err.into()),
}

As for whether it solves your problem, well you'd need to answer "what do you want to happen if create fails?"

1 Like

That is exactly what I mean. That clears up a bunch. Up to now i was thinking that ? returned the Result, so that all it did was move the problem, not provide a mechanism to deal with it. Now I see that by returning only the error it can be handled.

Thank you Alice!

1 Like

You're welcome!

@alice I am still not grasping handling recoverable errors. Yesterday I thought I had a grip on it when you showed me that ? passes back the error, but in trying it I discovered it can only do that in a function that returns a Result enum, so the effect is simply to move the Result handling up a level. It does not change the way it can be handled. Thus I am in the same place I was before we spoke yesterday, So I have lifted this example from the Book v2. What if I want to actually handle the error, not simply panic. In the book both recoverable and unrecoverable errors are demonstrated with error handling being a panic. For example I want to throw it back to the user to enter another file name. How do I do that in the error arm when it is expecting one of only returning a file handle or a panic?
Thanks again

use std::fs::File;

fn main() {
    let f = File::open("hello.txt");
    let f = match f {
        Ok(file) => file,
        Err(error) => panic!("Problem opening the file: {:?}", error),
    };
}

None of this has anything to do with ? or Result or what the function returns, really.

@alice is correct in saying that your problem seems to be that you don't specify how you want to handle the error, which makes us miss the point, the broader picture.

There is another, distinct problem: the "infinite" error handling you are running into is the consequence of how you think about the problem. Assuming you do indeed want to ask the user for another file name, you formulate the problem as:

  1. Try to open file
  2. If it fails, ask user for new filename
  3. Try to open new file
  4. If it fails, ask user for yet another filename
  5. Try to open 3rd file as well
  6. If it fails, ask for new filename again… etc.

Of course, you don't want to write code for this, it would indeed result in an infinitely-long piece of code, nested infinitely deeply as well. But you don't need that. You are doing the same thing over and over again. Doesn't that sound suspicious? What you need is a plain old loop:

use std::fs::File;
use std::io::{ stdin, stdout, Read, BufRead, Write };

fn main() {
    // get access to stdin and stdout
    let input = stdin();
    let mut input = input.lock();

    let output = stdout();
    let mut output = output.lock();

    // ask for file name until it is valid
    let mut file = loop {
        print!("Enter file name: ");
        output.flush().unwrap();

        let mut filename = String::new();
        input.read_line(&mut filename).unwrap();

        // trim trailing '\n' so that the file name is valid
        match File::open(filename.trim()) {
            Ok(file) => break file, // exit the loop with the opened file
            Err(_) => {} // do nothing, i.e. keep going
        }
    };

    // read contents of file and print it to stdout
    let mut contents = String::new();
    file.read_to_string(&mut contents).unwrap();

    println!("Contents:\n---------{}", contents);
}
4 Likes

This is correct. Often, the code that knows how to correct an error is in a different place than the code that originally detects the problem. The ? operator is designed to help you move the error from the code that has failed to the place that that can do something about it. At that point, you’ll need to use match or one of Result’s methods to actually implement the appropriate corrective action.

1 Like

A loop is probably the right solution here, but recursion might match @JDI's thinking more closely (untested):

/// Get a text string interactively from the terminal
fn ask_user(prompt:&str)->String
{
    let mut result = String::new();
    let mut output = stdout().lock();
    let mut input = stdin().lock();

    print!("{}", prompt);
    output.flush().unwrap();

    input.read_line(&mut result).unwrap();

    // Remove trailing whitespace (newline)
    let len = result.trim_end().len();      
    result.truncate(len);
    result
}

/// Continuously prompt user for a filename until one successfully opens.
fn open_user_file()->File {
    let filename = ask_user("Enter file name: ");
    File::open(filename).unwrap_or_else(|_err| open_user_file())
}
1 Like

Thank you @2e71828. I saw the need for a loop. That is not really what I was getting at. I suppose the reality that I have to accept, which is demonstrated in your code (thanks for that), is that error handling must at some point end with a panic. Whether explicitly by a call to panic in the error arm of a match or implicitly in an unwrap(). It is such a different model that I am just having trouble getting used to it. I will get there.

That was not what I meant to demonstrate, because it’s not true. You, the programmer, must decide what to do when errors occur in each instance:

  • You can panic! if there’s not a reasonable way to continue, like when the terminal input has been closed in input.read_line(&mut result).unwrap(); above.
  • You can give the error to another function to handle with the ? operator.
  • You can provide a default value to use in place of the one that couldn’t be generated with unwrap_or.
  • If you don’t need a value from the fallible operation, you can simply drop the Result, ignoring any possible errors.
  • You can use some alternative method to obtain the value you need.
File::open(filename).unwrap_or_else(|_err| open_user_file())

is a special case of this last category: its alternate strategy is simply trying again (via recursion). Except for the case where you can’t get user input anymore, this never panics, it just keeps endlessly asking the user for some other file to try.


This necessity to be explicit about what happens to each individual error is why @alice asked you this in her first reply:

Without the answer to this question (which you haven’t provided), it’s impossible for us to properly answer your original question— We simply don’t have the necessary information.

1 Like

No, that is a completely wrong takeaway. @2e71828 only wanted to demonstrate that a loop is equivalent with a recursive call.

I'm also curious why you are dissatisfied with my solution, which solves your problem and does not contain any panicking.

1 Like

Good day all,
I am not dissatisfied with anyone's solution. I have learned from all of them, and I do appreciate that. What I am not grasping well is the error handling model of Rust. I am after understanding, not necessarily code. As some hackles appear to have been raised it is perhaps best that we let this topic lay and I will try a different avenue to reach my understanding.

Thanks to all who have taken the time to help me. @alice @HC203 @2e71828