Hone logo
Hone
Problems

Implementing a Robust Result Type in Rust

Rust's Result enum is a cornerstone of its error-handling strategy, providing a safe and expressive way to manage operations that can either succeed or fail. This challenge asks you to implement a custom Result type that mirrors the core functionality of Rust's built-in Result, allowing you to practice fundamental enum, pattern matching, and generic programming concepts in Rust.

Problem Description

Your task is to create a generic enum named MyResult<T, E> that can represent either a successful value of type T or an error value of type E. This type will be used to signify the outcome of operations.

Key Requirements:

  1. Enum Definition: Define MyResult<T, E> with two variants:

    • Ok(T): Represents a successful operation with an associated value of type T.
    • Err(E): Represents a failed operation with an associated error of type E.
  2. is_ok() Method: Implement a method is_ok(&self) -> bool that returns true if the MyResult is Ok, and false otherwise.

  3. is_err() Method: Implement a method is_err(&self) -> bool that returns true if the MyResult is Err, and false otherwise.

  4. unwrap() Method: Implement a method unwrap(self) -> T that returns the contained Ok value. If the MyResult is Err, this method should panic with a descriptive message.

  5. unwrap_or(self, default: T) -> T Method: Implement a method unwrap_or(self, default: T) -> T that returns the contained Ok value. If the MyResult is Err, it should return the provided default value.

  6. map<U, F>(self, f: F) -> MyResult<U, E> Method: Implement a method map<U, F>(self, f: F) -> MyResult<U, E> where F: FnOnce(T) -> U. This method applies the function f to the contained Ok value, returning a new MyResult with the transformed value. If the MyResult is Err, the Err value is returned unchanged.

Expected Behavior:

  • The MyResult type should be capable of holding any type for its success value (T) and any type for its error value (E).
  • The methods should behave exactly as described above. Panicking should occur only when unwrap() is called on an Err.

Edge Cases to Consider:

  • What happens when unwrap() is called on an Err variant? (It should panic).
  • How does map handle an Err variant? (It should pass the Err through unchanged).

Examples

Example 1: Basic Usage

// Assume MyResult is defined and its methods are implemented
let success: MyResult<i32, &str> = MyResult::Ok(10);
let failure: MyResult<i32, &str> = MyResult::Err("Something went wrong");

assert_eq!(success.is_ok(), true);
assert_eq!(success.is_err(), false);
assert_eq!(failure.is_ok(), false);
assert_eq!(failure.is_err(), true);

Example 2: unwrap() and unwrap_or()

let success: MyResult<i32, &str> = MyResult::Ok(10);
let failure: MyResult<i32, &str> = MyResult::Err("Error message");

assert_eq!(success.unwrap(), 10);
assert_eq!(failure.unwrap_or(5), 5);

// The following would panic if uncommented and run:
// failure.unwrap();

Example 3: map()

let success: MyResult<i32, &str> = MyResult::Ok(10);
let failure: MyResult<i32, &str> = MyResult::Err("Error message");

let mapped_success: MyResult<i32, &str> = success.map(|x| x * 2);
assert_eq!(mapped_success, MyResult::Ok(20));

let mapped_failure: MyResult<i32, &str> = failure.map(|x| x * 2);
assert_eq!(mapped_failure, MyResult::Err("Error message"));

let string_success: MyResult<String, i32> = MyResult::Ok("hello".to_string());
let mapped_string_success: MyResult<usize, i32> = string_success.map(|s| s.len());
assert_eq!(mapped_string_success, MyResult::Ok(5));

Constraints

  • The generic types T and E can be any valid Rust types.
  • The methods should not have any specific performance overhead beyond what is inherent to Rust's enum and method dispatch.

Notes

  • You'll need to use impl<T, E> MyResult<T, E> { ... } to define methods for your generic enum.
  • Pay close attention to ownership and borrowing when implementing the methods, especially unwrap() and map().
  • Consider how you'll handle the panic in unwrap().
  • The map method requires a closure that takes T and returns U. The signature F: FnOnce(T) -> U is key here. FnOnce is appropriate because the closure is called at most once and may consume its argument.
  • You might find it useful to derive Debug and PartialEq for MyResult to make assertions in examples easier to write and verify.
Loading editor...
rust