GeistHaus
log in · sign up

https://sanj.ink/feed.xml

rss
10 posts
Polling state
Status active
Last polled May 18, 2026 22:33 UTC
Next poll May 20, 2026 00:23 UTC
Poll interval 86400s
ETag "694273c2-2559f"
Last-Modified Wed, 17 Dec 2025 09:11:30 GMT

Posts

Pi Hole Blocking Fortnite Gift Card Redemption

TL;DR: Turn off the Pi-Hole or use Mobile data to redeem Fortnite gift cards.

I’ve been trying to redeem some Fornite gift cards for my son recently and I’ve found it to be so frustrating.

I initially tried to use the QR on the gift card using my Android phone but that just did nothing. I later tried to enter the code on the redeem website manually. Same result - nothing. No error. No message. Nothing. I also tried this with multiple browsers on macOS (Brave and Safari) but no dice.

I contacted Epic support about this and it took them four days to get back to me. But one of their recommendations helped me to get the gift card working - Jumping off the wifi and using mobile data. Once I did this, the redemption went through seemlessly.

I did some digging around to see if this was a known problem and came across this post on Reddit.

Although I had a quick scan of my Pi-Hole Query logs, I couldn’t see anything specific to Epic; only some generic tracking sites that I would normally want to block every time.

Query Log
Query Log

Unfortunately for the moment, using Mobile data or turning off the Pi-Hole temporarily seems to be the only solution.

https://blog.ssanj.net/posts/2025-12-17-pi-hole-blocking-fornite-gift-card-redemption.html
Extensions
How to call trait functions in Rust

Traits define a group of functions that work towards some shared behaviour. There are a few ways to invoke trait functions in Rust and we’ll have a look at those ways in the sections below.

Traits have two types of functions:

  1. Methods - These are functions that take in a self parameter
  2. Associated Functions - These are functions that do not take a self parameter
How to invoke Functions of a Trait

Here are the standard ways of invoking trait functions:

  1. Using an instance of the implementing type of the trait (for methods)
  2. Using the type that implements the trait
  3. Using the trait
  4. Using the fully qualified implementation path to the function
An Example

Let’s take the following trait that converts a type to its lowercase equivalent as an example:

trait Lower {
  fn lower(&self) -> Self;
}

Using the above trait, we can convert from an implementing type to a lowercase version of itself.

In summary:

Implementation type: Self
Trait: `Lower`
Method: `lower`
Function parameter type: `&self`

Given, a simple wrapper type Identifier over a String:

struct Identifier(String);

And an implementation for the Lower trait for Identifier:

impl Lower for Identifier {
  fn lower(&self) -> Self {
    Self(self.0.to_lowercase())
  }
}

How do we go about invoking the functions on the Lower trait?

Using an instance of the implementing type of the trait

This only works for trait methods; functions that take in a self parameter.

The format of calling a function on a implementation instance is:

<implementation type instance>.function(param)

In our case:

Implementation type: `Identifier`
Function: `lower`
Function parameter: `&self` // This is needed for us to call the function on the instance

Given an instance of an Identifier:

// Create an Identifier instance
let id_string = String::from("012BCE5");
let id = Identifier(id_string)

We can call the lower method on an instance of Identifier as follows:

let id1 = id.lower();
Using the type that implements the trait

The format of calling a function on a type is:

<Implementation type>::function(param)

In our case:

Implementation type: `Identifier`
Function: `lower`
Function parameter: `&self`

Which gives us:

Identifier::lower(&id)
Using the trait

The format of calling a function on a trait is:

<trait>::function(param)

In our case:

Trait: `Lower`
Function: `lower`
Function parameter: `&self` // Any implementation type of Lower

Which gives us:

Lower::lower(&id)
Using the fully qualified implementation path to the function

The format of calling the fully qualified path to the function on a trait is:

<Implementation Type as Trait>::function(param)

In our case:

Implementation Type: Identifier
Trait: `Lower`
Function: `lower`
Function parameter: `&self` // Accepts only `Identifier` since we are using the fully qualified path

Which gives us:

<Identifier as Lower>::lower(&id)
Using From

Now that we know the basics of calling trait functions, let’s look at how we can use the From trait from the std library.

From is defined as:

pub trait From<T>: Sized {
    /// Converts to this type from the input type.
    fn from(value: T) -> Self;
}

Using the above trait, we can convert from a value of some source type T to the implementing type (Self). The trait function in this case is the from function. We can think of the from function as going from a type T -> Self.

We don’t have a self parameter on the from function. This will restrict how we can call this function.

In summary:

Implementation type: Self (Depends on the implementing type)
Trait: `From`
Function: from
Function parameter type: `T` (Source type)

Let’s implement the From trait for Identifier, so that it converts a String to an Identifier:

impl From<String> for Identifier {
  fn from(value: String) -> Self {
    Identifier(value)
  }
}

In summary:

Implementation type: `Identifier`
Trait: `From<T>`, where `T` is `String`
Function: `from`
Function parameter: `String`
Using an instance of the implementing type of the trait

The format of calling a function on an implementation instance is:

<Implementation instance>.function(param)

In our case:

Implementation type: `Identifier`
Parameter type: `String`
Function: `from`
Function parameter: `String`

As mentioned previously as we don’t have a self parameter to this function, we can’t use it on the instance of the implementation type.

Now while it would seem that we can’t do the conversion from String -> Identifier via an instance, Rust has some supports that lets us do that.

Rust implements the Into trait for each implementation of the From trait for free. The Into trait is defined as:

pub trait Into<T>: Sized {
  /// Converts this type into the (usually inferred) input type.
  fn into(self) -> T;
}

If you define From for a type, you get Into for that same type by the compiler with the following implementation:

impl<T, U> Into<U> for T
where
    U: From<T>,
{
    /// Calls `U::from(self)`.
    ///
    /// That is, this conversion is whatever the implementation of
    /// <code>[From]<T> for U</code> chooses to do.
    fn into(self) -> U {
        U::from(self)
    }
}

In summary:

Implementation type: `T`
Trait: Into<U> // where U is the implementer of `From<T>`
Function: into
Function parameter: self

In our case for converting from String -> Identifier:

Implementation type: `String`
Trait: Into<Identifier>
Function: into
Function parameter: String

The implementation above, takes two type parameters: T and U. The U should have a From implementation for T and if it does, when calling the into method on an instance of self, invokes the conversion from T -> U through U::from(self).

Don’t worry if this doesn’t make sense just yet, but try to remember that if you implement the From trait you get a free into function that you can invoke on an instance of the parameter type of the from function.

Given that the into function takes a self reference, and in our example self is a String, we can call it on a String instance:

let id1: Identifier = id_string.into();

The above works! Yay!

If we leave off the Identifier type annotation on id1:

let id1 = id_string.into();

We get the following compilation error:

error[E0283]: type annotations needed
   --> src/main.rs:138:9
    |
138 |     let id1 = id_string.into();
    |         ^^^                     ---- type must be known at this point
    |
note: multiple `impl`s satisfying `_: From<String>` found
   --> src/main.rs:84:1
    |
84  | impl From<String> for Identifier {
    | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    = note: and more `impl`s found in the following crates: `alloc`, `std`:
            - impl From<String> for Arc<str>;
            - impl From<String> for Box<(dyn std::error::Error + 'static)>;
            - impl From<String> for Box<(dyn std::error::Error + Send + Sync + 'static)>;
            - impl From<String> for Box<str>;
            - impl From<String> for OsString;
            - impl From<String> for PathBuf;
            - impl From<String> for Rc<str>;
            - impl From<String> for Vec<u8>;
    = note: required for `String` to implement `Into<_>`
help: consider giving `id1` an explicit type
    |
138 |     let id1: /* Type */ = id_string.into();
    |            ++++++++++++

For more information about this error, try `rustc --explain E0283`.

The compiler is letting us know that there are many From implementations for String and we need to give it a hint on which one we want to use:

help: consider giving `id1` an explicit type
    |
138 |     let id1: /* Type */ = id_string.into();
    |            ++++++++++++
Using the type that implements the trait

The format of calling a function on a type is:

<Implementation type>::function(param)

In our case:

Implementation type: `Identifier`
Function: `from`
Function parameter: `String`

Running the conversion:

let id2 = Identifier::from(id_string)

We don’t need to be explicit about the type of id2 as the compiler can figure out what the type is.

Using the trait

The format of calling a function on a trait is:

<trait>::function(param)

In our case:

Trait: `From<String>`
Function: `from`
Function parameter: `String`

Running the conversion:

let id3: Identifier = From::from(id_string);

If we leave out the type of id3 we get the following compilation error:

error[E0790]: cannot call associated function on trait without specifying the corresponding `impl` type
   --> src/main.rs:144:15
    |
144 |     let id3 = From::from(id_string);
    |               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ cannot call associated function of trait
    |
help: use a fully-qualified path to a specific available implementation
    |
144 |     let id3 = </* self type */ as From>::from(id_string);
    |               +++++++++++++++++++     +

For more information about this error, try `rustc --explain E0790`.

Given there are multiple From instances in scope, the compiler can’t narrow it down until we give it more information.

The compilation error hints at using the full qualified path to the implementation (type as trait) format:

help: use a fully-qualified path to a specific available implementation
    |
144 |     let id3 = </* self type */ as From>::from(id_string);
    |               +++++++++++++++++++     +

We’ll look at that next.

Using the fully qualified implementation path to the function

The format for using the fully qualified path to the implementation is:

<implementation type as trait>::function(param)

In our case:

Implementation type: `Identifier`
Trait: `From<String>`
Function: from
Function parameter: `String`
let id4 = <Identifier as From<String>>::from(id_string);

And in the above case as well, we can leave off the type of id4 because we have been explicit as possible about which implementation of From to use.

https://blog.ssanj.net/posts/2024-07-17-how-to-call-trait-functions-in-rust.html
Extensions
Working With Rust Result - Extracting Values That Can Panic - Part 3

Note: Do not use these functions if you have better/nicer alternatives.

unwrap

What if we want to fail (panic) our program if the supplied age is not twenty five?

We can work forcibly by using unwrap. unwrap is defined as:

pub fn unwrap(self) -> T
where
    E: fmt::Debug,

The above definition returns the success type T under all conditions. But how can it return a success value T if it’s an Err instance with a value of type E?

// pseudocode
// Given: Result<T, E>

Ok(T)  -> T
Err(E) -> T // How do we do this?

Unwrap’s implementation demonstrates how this is achieved:

pub fn unwrap(self) -> T
where
    E: fmt::Debug,
{
    match self {
        Ok(t) => t,
        Err(e) => unwrap_failed("called `Result::unwrap()` on an `Err` value", &e),
    }
}

fn unwrap_failed(msg: &str, error: &dyn fmt::Debug) -> ! {
    panic!("{msg}: {error:?}")
}

On an Err, the unwrap_failed function is called, which panics. Since panic doesn’t have a type, the never type:!, coerces the result of unwrap_failed to match type T. This explains how we can always return an value of type T even when we don’t have one.

Since we don’t have some sort of default value for T supplied, this function panics when the result is an Err:

let twenty_five_1: u8 = twenty_five(25).unwrap(); // This works because the result is 'Ok'

let twenty_five_2: u8 = twenty_five(20).unwrap(); // This goes boom! because the result is 'Err'
//thread 'main' panicked at src/main.rs:9:22:
//called `Result::unwrap()` on an `Err` value: "20 is not 25!"

Also note that the error type E has to have an instance of the Debug trait. This is so that the error can be written out if the unwrap causes a panic:

called `Result::unwrap()` on an `Err` value: "20 is not 25!"
expect

What if we wanted to customize the error message when we failed dramatically throwing a panic?

We can do that by using the expect method. expect is defined as:

pub fn expect(self, msg: &str) -> T
where
    E: fmt::Debug,
{
    match self {
        Ok(t) => t,
        Err(e) => unwrap_failed(msg, &e),
    }
}

Similar to the definition for unwrap, a success type of T is always returned or the function panics, but this time with a message we supply:

let twenty_five_1: u8 = twenty_five(25).expect("Ooops! Looks like you're not twenty five"); // This works because the result is 'Ok'

let twenty_five_2: u8 = twenty_five(20).expect("Ooops! Looks like you're not twenty five"); // This goes boom! because the result is 'Err'
//thread 'main' panicked at src/main.rs:9:22:
//Ooops! Looks like you're not twenty five: "20 is not 25!"

It’s important to note that the value in the Err: “20 is not 25!” is still printed but we get to customize the message preceding it:

Ooops! Looks like you're not twenty five

Panic-ing your program is probably the last thing you want to do; It’s something you do when you have no other options. As such it’s highly discouraged. We should only panic when we have no other ways of recovering from the error.

But how do you do that? We’ve already seen some ways to do that with pattern matching, map_or_else and map_or. We will look at nicer ways to unwrap a Result next.

https://blog.ssanj.net/posts/2024-01-24-working-with-rust-result-part-3.html
Extensions
Working With Rust Result - Making Things Nicer with Fallbacks - Part 4

We can unwrap a Result in a nicer way, if we provide a default value of type T or call a function that returns a value of type T when given a type E:

// pseudocode
// Given: Result<T, E>

Ok(T)  -> T
Err(_) -> T // Return value of `T`
Err(E) -> T // Use a function of type `E` -> `T`
unwrap_or

unwrap_or is defined as:

pub fn unwrap_or(self, default: T) -> T {
    match self {
        Ok(t) => t,
        Err(_) => default,
    }
}

In summary:

// pseudocode
// Given: Result<T, E>
// Return type: T
// default is eager

default: -> T

Ok(t)   ->  t       -> T // Return value in Ok
Err(e)  ->  default -> T // Return default if in error

In the above definition we supply a default value of type T. This default value will be used when there is an Err, the Ok value will be returned otherwise. This is very similar to map_or but where we don’t run a function on the success value.

Since default is eager it will get evaluated as soon as unwrap_or is called. Values for default should only be constants and precomputed values.

Here’s an example of using unwrap_or to do just that:

let twenty_five_or_ten_1: u8 = twenty_five(20).unwrap_or(10); // 10
let twenty_five_or_ten_2: u8 = twenty_five(25).unwrap_or(10); // 25
unwrap_or_else

There’s a similarly named function called unwrap_or_else. The main difference being that unwrap_or_else takes in a function op that is called when an Err is returned:

pub fn unwrap_or_else<F: FnOnce(E) -> T>(self, op: F) -> T {
    match self {
        Ok(t) => t,
        Err(e) => op(e),
    }
}

In summary:

// pseudocode
// Given: Result<T, E>
// Return type: T

op: E -> T

Ok(t)   ->  t     -> T // Return value in Ok
Err(e)  ->  op(e) -> T // Convert the value in Err to a `T`

This is very similar to the map_or_else function but where a function is only applied to the error case and not the success case.

unwrap_or_default

Another in the same family of functions is unwrap_or_default, which is defined as:

pub fn unwrap_or_default(self) -> T
where
    T: Default,
{
    match self {
        Ok(x) => x,
        Err(_) => Default::default(),
    }
}

In the above definition, if a Result is an Err then the default instance of type T is used. The type T has a constraint on it that requires that it has an instance of the Default trait: T: Default.

In summary:

// pseudocode
// Given: Result<T, E>
// Return type: T

T: Default     -> T

Ok(t)   ->  t  -> T // Return value in Ok
Err(_)         -> T // Return Default instance for T

Here’s an example of how to use it:

let result_ok: Result<u32, String> = Ok(1);
let result_err: Result<u32, String> = Err("You have errors".to_owned());

result_ok.unwrap_or_default();  // 1
result_err.unwrap_or_default(); // 0

This is also very similar to unwrap_or where, we supply a default value for the error case. With unwrap_or_default the default value is derived from the Default instance for type T.

https://blog.ssanj.net/posts/2024-01-24-working-with-rust-result-part-4.html
Extensions
Working With Rust Result - Tranforming Values - Part 5

When using functions like map_or_else, we extracted the success and error values out of a Result, thereby losing our Result wrapper. What if you could run a function on the value within a Result and stay within the Result wrappers? Then you wouldn’t have to do all this pesky unwrapping until you needed the value.

map

The map function lets you transform a value within a Result:

pub fn map<U, F: FnOnce(T) -> U>(self, op: F) -> Result<U, E> {
    match self {
        Ok(t) => Ok(op(t)),
        Err(e) => Err(e),
    }
}

In the above definition, the supplied function op is only run on the value within the Ok instance and the error value within the Err instance is left untouched.

// pseudocode
// Given: Result<T, E>
// Return type: Result<U, E>

op: T -> U // Convert success value to a U

Ok(t:T)   ->  op(t) -> U -> Ok(U)  // Return converted value in Ok, as a Result<U, E>
Err(e:E)                 -> Err(e) // Return existing error as Result<U, E>

After function op is used, the result is rewrapped in an Ok constructor. In the Err case we also rewrap the error again. This might seem pointless, but this has to be done because the result type is changing from a Result<T, E> to a Result<U, E> and the Err(e) in the pattern match is of type Result<T, E>. By creating a new Err instance we convert the error to type Result<U, E>.

In either case the Resultis converted from a Result<T, E> to a Result<U, E>. It’s important to note that we stay within a Result after running the function op. Here’s a simple example demonstrating this:

  let result_ok_1: Result<u32, String> = Ok(1);
  let result_ok_2: Result<u32, String> = result_ok_1.map(|n| n * 2); // Ok(2), multiplied by 2
  let result_ok_3: Result<String, String> = result_ok_2.map(|n| format!("age: {}", n)); // Ok("age: 2"), converted to a String

  let result_err_1: Result<u32, String> = Err("You have errors".to_owned());
  let result_err_2: Result<u32, String> = result_err_1.map(|n| n * 2); // Err("You have errors"), no change
  let result_err_3: Result<String, String> = result_err_2.map(|n| format!("age: {}", n)); // Err("You have errors"), no change

You can also think of the map function as of type: Result<T -> U, E>; as in it runs a function on the success side of Result leaving the error side untouched.

https://blog.ssanj.net/posts/2024-01-24-working-with-rust-result-part-5.html
Extensions
Working With Rust Result - Combining Results - Part 6

Result gets interesting when you need to combine multiples to give you one final Result.

and_then

Sometimes when you have multiple functions that return Results, you want to know if all of them succeeded or any of them failed. and_then can help you there. and_then is defined as:

pub fn and_then<U, F: FnOnce(T) -> Result<U, E>>(self, op: F) -> Result<U, E> {
    match self {
        Ok(t) => op(t),
        Err(e) => Err(e),
    }
}

From the above definition, the function op is run on the success value within an Ok instance. This is very similar to map, with the main difference being that the function op returns another Result instead of another type. It’s important to note that since op returns a Result we can choose whether to return an Ok or Err instance at this point. and_then gives us the power to make a decision.

Unlike map there is no wrapping of the result in an Ok constructor as op already returns a Result.

// pseudocode
// Given: Result<T, E>
// Return type: Result<U, E>

op: T -> Result<U, E> // Converts a success value into another Result (Ok or Err)

Ok(t:T)   ->  op(t)  -> Ok(U) or Err(E) // Return converted value in Ok or Err as a Result<U, E>
Err(e:E)             -> Err(e)          // Return existing error as Result<U, E>

Given the following function that parses a string to a u32 or returns a ParseIntError:

use std::num::ParseIntError;
use std::str::FromStr;

fn parse_number(value: &str) -> Result<u32, ParseIntError> {
  u32::from_str(value)
}

Let’s try and parse a string number with parse_number and multiply its value by 2:

parse_number("10") // Result<u32, ParseIntError>
    .and_then(|value| {
        // We have successfully parsed "10" into 10.
        let new_result = ten * 2; // Multiply by 2
        todo!() // What do we return here?
    })

Given that we have to use a function that also returns a Result from and_then we can wrap new_result in the Ok constructor:

parse_number("10") // Result<u32, ParseIntError>
    .and_then(|ten| {
        // We have successfully parsed "10" into 10.
        let new_result = ten * 2 // Multiply by 2
        Ok(new_result) // Result<u32, ParseIntError>
    })
Aligning error types

If we want to fail our calculation for some reason we can return an Err:

struct MyError(String);

parse_number("10") // Result<u32, ParseIntError>
.and_then(|one| {
    // We have successfully parsed "10" into 10.
    parse_number("20")
      .and_then(|two| {
          // We have successfully parsed "20" into 20.
          // but we don't like even numbers...
          if two % 2 == 0 {
              Err(MyError("I don't add even numbers".to_owned())) // Result<u32, MyError>
          } else {
              Ok(one + two) // Result<u32, ParseIntError>
          }
      })
})

But we can an error!:

error[E0308]: mismatched types
   --> src/main.rs:86:23
    |
86  |                   Err(MyError("I don't add even numbers".to_owned()))
    |                   --- ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ expected `ParseIntError`, found `MyError`
    |                   |
    |                   arguments to this enum variant are incorrect
    |
help: the type constructed contains `MyError` due to the type of the argument passed
   --> src/main.rs:86:19
    |
86  |                   Err(MyError("I don't add even numbers".to_owned()))
    |                   ^^^^----------------------------------------------^
    |                       |
    |                       this argument influences the type of `Err`

Which points to the real cause:

expected ParseIntError, found MyError

What this means is that when you are chaining Results through and_then functions, all the Err types need to be the same. We can change the Ok type as much as we want but we have to align the Err types. This is just something to keep in mind when using Result. If you have functions that return Results with different Err types, you can create a common error type and convert each error into that type using something like map_err, which we will cover later.

For completeness, here’s how you align your error types with map_error:

parse_number("10")
  .map_err(|e| MyError(e.to_string())) // Result<u32, MyError>
  .and_then(|one| {
    // We have successfully parsed "10" into 10.
    parse_number("20")
      .map_err(|e| MyError(e.to_string())) // Result<u32, MyError>
      .and_then(|two| {
          // We have successfully parsed "20" into 20.
          // but we don't like even numbers...
          if two % 2 == 0 {
              Err(MyError("I don't add even numbers".to_owned())) // Result<u32, MyError>
          } else {
              Ok(one + two)
          }
      })
})

https://blog.ssanj.net/posts/2024-01-24-working-with-rust-result-part-6.html
Extensions
Working With Rust Result - Chaining with Map - Part 7

What if we only wanted to parse two numbers and add them together and not return any errors? We can already solve this with and_then as before:

parse_number("10")
  .and_then(|ten| {
      // We have successfully parsed "10" into 10.
      parse_number("20")
        .and_then(|twenty| {
            // We have successfully parsed "20" into 20.
            Ok(ten + twenty)
        })
  })

We could also just map over the last function that returns a Result:

parse_number("10")
  .and_then(|ten| {
      // We have successfully parsed "10" into 10.
      parse_number("20")
        .map(|twenty| { // We map here
            // We have successfully parsed "20" into 20.
            ten + twenty // We didn't have to wrap the answer in a Result, because we are 'in' a Result
        })
  })

Reminder about maps definition:

pub fn map<U, F: FnOnce(T) -> U>(self, op: F) -> Result<U, E> {
    match self {
        Ok(t) => Ok(op(t)),
        Err(e) => Err(e),
    }
}

map wraps the result of op in an Ok constructor for us so we don’t have to!

In summary:

// pseudocode for map
// Given: Result<T, E>
// Return type: Result<U, E>

op: T -> U // Convert success value to a U

Ok(t:T)   ->  op(t) -> U -> Ok(U)  // Return converted value in Ok, as a Result<U, E>
Err(e:E)                 -> Err(e) // Return existing error as Result<U, E>

How do we decide when to use and_then at the last step of a Result chain or whether to use map?

If you need to make a decision about whether to fail or not, then use and_then because you can return an Ok to succeed or an Err to fail. If you simply want to work on the Ok side of a previous Result, then use map.

This logic works only at the last step of a Result chain. If you use map where you should have used and_then, you will end up with a nested Result of the sort: Result<Result<T, E>,E> indicating that you should have and_thened where you had previously mapped.

So many rules to keep in mind! If only there were an easier way to combine Results.

https://blog.ssanj.net/posts/2024-01-24-working-with-rust-result-part-7.html
Extensions
Working With Rust Result - Combining Results with the Question Mark Operator - Part 8

Lets try to perform a calculation on multiple numbers parsed from strings:

let numbers_1: Result<u32, ParseIntError> = add_numbers("10", "20", "30"); // Ok(60)
let numbers_2 = add_numbers("ten", "20", "30");    // Err(ParseIntError { kind: InvalidDigit })
let numbers_3 = add_numbers("10", "twenty", "30"); // Err(ParseIntError { kind: InvalidDigit })
let numbers_4 = add_numbers("10", "20", "thirty"); // Err(ParseIntError { kind: InvalidDigit })

Here’s the definition of add_numbers:

fn add_numbers(one: &str, two: &str, three: &str) -> Result<u32, ParseIntError> {
  parse_number(one) // try and get the first number. Returns Result<u32, ParseIntError>
    .and_then(|n1| { // if that succeeds,
      parse_number(two) // try and get the second number. Returns Result<u32, ParseIntError>
        .and_then(|n2| { // if that succeeds
          parse_number(three) // try and get the third number. Returns Result<u32, ParseIntError>
            .map(|n3| n1 + n2 + n3) // if that succeeds, add up all the previous numbers. Returns Result<u32, ParseIntError>
        })
    })
}

This is similar to how we previously parsed two numbers. This is quickly becoming hard to reason about. Parsing more numbers like this would be almost unmaintainable. Luckily Rust gives us a simpler way to do this.

The question mark operator

Rust has the question mark operator (?) which allows you to simply return an error or extract a success value. You can think of it as an unwrap on Ok with an immediate return on Err, instead of panic-ing.

Here’s the definition of and_numbers_2 which uses the ? operator:

fn add_numbers_2(one: &str, two: &str, three: &str) -> Result<u32, ParseIntError> {
  let n1: u32 = parse_number(one)?;   // Get the number or return an Err
  let n2: u32 = parse_number(two)?;   // Get the number or return an Err
  let n3: u32 = parse_number(three)?; // Get the number or return an Err

  // If we got here, all the numbers are valid
  Ok(n1 + n2 + n3) // Add all the numbers and return an Ok
}

It’s important to note that if any of the parse_number function calls return an Err, the add_numbers_2 function would return that Err as the final result instead of proceeding to the next line.

We have to still wrap the final result in an Ok constructor as add_numbers_2 returns a Result<u32, ParseIntError>.

We can see that the add_numbers_2 function is easier to reason about than chaining together and_then and map calls as in the add_numbers function. The ? operator is supported for Result and Option types at the moment.

Keep aligning those error values

Something else to keep in mind is that we still need to align on the Err value as we did when using and_then:

fn add_numbers_3(one: &str, two: &str, three: &str) -> Result<u32, ParseIntError> {
  let n1: u32 = parse_number(one)?;                                      // Result<u32, ParseIntError>
  let n2: u32 = parse_number(two).map_err(|e| MyError(e.to_string()))?;  // Result<u32, MyError>
  let n3: u32 = parse_number(three)?;                                    // Result<u32, ParseIntError>

  // If we got here, all the numbers are valid
  Ok(n1 + n2 + n3) // Add all the numbers and return an Ok
}

The above leads to an error:

error[E0277]: `?` couldn't convert the error to `ParseIntError`
   --> src/main.rs:414:74
    |
412 | ...numbers_3(one: &str, two: &str, three: &str) -> Result<u32, ParseIntError> {
    |                                                    -------------------------- expected `ParseIntError` because of this
413 | ...1: u32 = parse_number(one)?; // Get the number or return an Err
414 | ...2: u32 = parse_number(two).map_err(|_| MyError("Blah".to_owned()))?; // Get the number...
    |             ----------------- ---------------------------------------^ the trait `From<MyError>` is not implemented for `ParseIntError`, which is required by `Result<u32, ParseIntError>: FromResidual<Result<Infallible, MyError>>`
    |             |                 |
    |             |                 this can't be annotated with `?` because it has type `Result<_, MyError>`
    |             this has type `Result<_, ParseIntError>`
    |
    = note: the question mark operation (`?`) implicitly performs a conversion on the error value using the `From` trait
    = help: the following other types implement trait `FromResidual<R>`:
              <Result<T, F> as FromResidual<Yeet<E>>>
              <Result<T, F> as FromResidual<Result<Infallible, E>>>
    = note: required for `Result<u32, ParseIntError>` to implement `FromResidual<Result<Infallible, MyError>>`

The important bits are:

error[E0277]: ? couldn’t convert the error to ParseIntError

Result<u32, ParseIntError> ————————– expected ParseIntError because of this

the trait From<MyError> is not implemented for ParseIntError, which is required by Result<u32, ParseIntError>: FromResidual<Result<Infallible, MyError>>

the question mark operation (?) implicitly performs a conversion on the error value using the From trait

The error states that we need an Err value of type ParseIntError and we have an Err value of type MyError. If we have a From instance to convert from MyError to ParseIntError it would be called and the conversion automatically performed for us.

We can’t directly create a ParseIntError as the constructor is private. We can however create one from parsing a String that doesn’t represent a number. Using that information we can create a terrible From implementation to convert from MyError to ParseIntError:

impl From<MyError> for ParseIntError {
  fn from(source: MyError) -> Self {
      parse_number(&source.0).unwrap_err() // Forcing values again
  }
}

With the above conversion in place add_numbers_3 compiles with out any errors, indicating that MyError was implicitly converted to ParseIntError and aligning our Err values almost for “free”. The question mark operator makes working with Result so much easier.

https://blog.ssanj.net/posts/2024-01-24-working-with-rust-result-part-8.html
Extensions
Working With Rust Result - Combining Results Some More - Part 9

There are even more ways to combine Resultss!

and

and is similar to and_then except a default Result is returned on an Ok instance:

pub fn and<U>(self, res: Result<U, E>) -> Result<U, E> {
    match self {
        Ok(_) => res,
        Err(e) => Err(e),
    }
}

Notice that the value inside the Ok instance is never used:

Ok(_) => res,

Since res is eager it will get evaluated as soon as and is called. Values for res should only be constants and precomputed values.

In summary:

// pseudocode
// Given: a Result<T, E>
// Return typr: Result<U, E>
// res is eager

Ok(_:T)  -> res:Result<U, E> -> Result<U, E>  // `Ok` value type changes from `T` from `U`
Err(e:E) -> Err(e)           -> Result<U, E>  // Notice that the `Err` value type is fixed at: `E`

This can be useful when you only want to know if something succeeded or failed instead of needing to work on its value.

Take creating a directory and returning a Success value as an example.

enum FileCreation {
  Success,
  Failure
}

We can create a directory with the create_dir function from the std::fs module:

fn create_dir<P: AsRef<Path>>(path: P) -> io::Result<()>

Notice how this function returns a Result with a unit as the success value.

If we use map to complete the example use case:

fn create_directory_map(dir_path: &Path) -> io::Result<FileCreation> {
  create_dir(dir_path)
    .map(|_| { // We ignore the value from create_dir
        FileCreation::Success
    }) // Result<FileCreation>
}

We have to ignore the previous success value in map (as we can’t do anything useful with unit). This is a little verbose and we can trim it down with and:

fn create_directory_and(dir_path: &Path) -> io::Result<FileCreation> {
  create_dir(dir_path)
    .and(Ok(FileCreation::Success))  // Result<FileCreation>
}
or

If you wanted to try an alternative Result on Err and you didn’t care about the error value, you could use or. or is defined as:

 pub fn or<F>(self, res: Result<T, F>) -> Result<T, F> {
   match self {
     Ok(v) => Ok(v),
     Err(_) => res,
   }
 }

In the definition above the value res is used only when there is an Err instance. If the Result is an Ok instance, its value is returned.

Since res is eager it will get evaluated as soon as or is called. Values for res should only be constants and precomputed values.

In summary:

// pseudocode
// Given: Result<T, E>
// Return type: Result<T, F>
// res is eager

Err(_:E) -> res:Result<T, F>  -> Result<T, F> // The `Err` value type changes from `E` to `F`
Ok(t:T)  -> Ok(t)             -> Result<T, F> // `Ok` value type is fixed: `T`

It’s important to note that res dictates the final Err type returned from or and that the type inside the Ok constructor doesn’t change. We’ll see that come into play in the example below.

Take the example of reading some configuration from a file or returning a default.

We can read from a file with the read_string function in the std::fs module:

pub fn read_to_string<P: AsRef<Path>>(path: P) -> Result<String>

We can read the config file or return a default with:

fn read_config(config_file: &str) -> Result<String, MyError> {
  use std::fs;
  let default_config: String = "verbose=true".to_owned();

  fs::read_to_string(config_file) // Result<String, std::io::Error>
    .or(Ok(default_config)) // Result<String, MyError>
}

The function res, passed to or dictates the final Err type. In the above example our error type has changed from std::io::Error to MyError. Also when chaining multiple or calls, the final res block dictates the final Result type. In the case of or chaining, the Ok type is fixed but the Err type can vary!

or_else

or_else is similar to or with the exception that you get access to the error type E and the op parameter is lazy:

  pub fn or_else<F, O: FnOnce(E) -> Result<T, F>>(self, op: O) -> Result<T, F> {
      match self {
          Ok(t) => Ok(t),
          Err(e) => op(e),
      }
  }

The function op takes in the Err type E and returns a Result with the same success type T and a new error type F:

FnOnce(E) -> Result<T, F>

In summary:

// pseudocode
// Given: Result<T, E>
// Return type: Result<T, F>

Err(e:E) -> op(e)  -> Result<T, F> // `Err` value type goes from `E` -> `F`
Ok(t:T)  -> Ok(t)  -> Result<T, F> // `Ok` value type is fixed: `T`

Here’s an example of where we can try one of several parse functions until we find one that succeeds.

Given a common error type MyError and a common success type MyResult:

#[derive(Debug)]
struct MyError(String);

#[derive(Debug)]
enum MyResult {
  N(u32),
  B(bool),
  S(String),
}

And functions to parse numbers and booleans:

fn parse_number(value: &str) -> Result<u32, ParseIntError> {
  u32::from_str(value)
}

fn parse_bool(value: &str) -> Result<bool, ParseBoolError> {
  bool::from_str(value)
}

One thing to note is that both functions return different error types in Err: ParseIntError and ParseBoolError respectively.

How would we combine these functions into parsing a string slice into a type of MyResult? And we also don’t support converting a string that is all caps into MyResult. That would be an error.

Similar to or, the function op, passed to or_else dictates the final Err type. When chaining multiple or_else calls, the final op call dictates the final Result type. In the case of or_else chaining, the Ok type is fixed but the Err type can vary.

Note that we don’t need to align the error types here as mentioned before because the Result passed to or_else would change the final Err type as required.

Here’s one way we could do it:

fn parse_my_result(value: &str) -> Result<MyResult, MyError> {
  parse_number(value)
    .map(|n| MyResult::N(n))
    .or_else(|_|
      parse_bool(value)
        .map(|b| MyResult::B(b))
    )
    .or_else(|_|
      if value.to_uppercase() == value {
          // We don't support full screaming caps
          Err(MyError(format!("We don't support screaming case: {}", value)))
       } else {
        Ok(MyResult::S(value.to_owned()))
       }
    )
}

We could use it like:

let r1: Result<MyResult, MyError> = parse_my_result("123"); // Ok(N(123))
let r2: Result<MyResult, MyError> = parse_my_result("true"); // Ok(B(true))
let r3: Result<MyResult, MyError> = parse_my_result("something"); //Ok(S("something"))
let r4: Result<MyResult, MyError> = parse_my_result("HELLO"); //Err(MyError("We don't support screaming case: HELLO"))

How the Err type changed between ParseIntError, ParseBoolError to MyError can be a bit harder to see. Here’s a more detailed example of the above:

fn parse_my_result_2(value: &str) -> Result<MyResult, MyError> {
  let p1: Result<MyResult, ParseIntError> = parse_number(value)
    .map(|n| MyResult::N(n));

  let p2: Result<MyResult, ParseBoolError> =  parse_bool(value)
        .map(|b| MyResult::B(b));

  let p3: Result<MyResult, MyError> =
    if value.to_uppercase() == value {
        // We don't support full screaming caps
        Err(MyError(format!("We don't support screaming case: {}", value)))
     } else {
      Ok(MyResult::S(value.to_owned()))
     };

    let r1: Result<MyResult, ParseBoolError> = p1.or_else(|_| p2);
    let r2: Result<MyResult, MyError> = r1.or_else(|_|p3);

    r2
}

Would could also write the above example with or since we have precomputed all the values before using or_else:

fn parse_my_result_3(value: &str) -> Result<MyResult, MyError> {
  let p1: Result<MyResult, ParseIntError> = parse_number(value)
    .map(|n| MyResult::N(n));  // Already evaluated

  let p2: Result<MyResult, ParseBoolError> =  parse_bool(value)
        .map(|b| MyResult::B(b));  // Already evaluated

  let p3: Result<MyResult, MyError> =
    if value.to_uppercase() == value {
        // We don't support full screaming caps
        Err(MyError(format!("We don't support screaming case: {}", value)))
     } else {
      Ok(MyResult::S(value.to_owned()))
     }; // Already evaluated

    let r1: Result<MyResult, ParseBoolError> = p1.or(p2); // Using or
    let r2: Result<MyResult, MyError> = r1.or(p3);  // Using or

    r2
}
https://blog.ssanj.net/posts/2024-01-24-working-with-rust-result-part-9.html
Extensions
Working With Rust Result

Trying to learning how to use the Rust Result type can be daunting. In this “Working with Rust Result” series of short posts, I hope to make that more approachable. This series is for beginners who are finding it difficult to understand what a Result is and how to use it.

The series is split into fourteen parts as listed below.

  1. What is a Result? - (Construction)
  2. Extracting Values - (Pattern matching, map_or_else, map_or)
  3. Extracting Values That Can Panic - (unwrap, expect)
  4. Making Things Nicer with Fallbacks - (unwrap_or, unwrap_or_else, unwrap_or_default)
  5. Transforming Values - (map)
  6. Combining Results - (and_then, Aligning error types)
  7. Chaining with Map
  8. Combining Results the Question Mark Operator - (The question mark operator, Keep aligning those error values)
  9. Combining Results Some More - (and, or, or_else)
  10. Working with Errors - (map_err, unwrap_err, expect_err)
  11. Conversion to Option - (ok, err, transpose)
  12. Value Tests - (is_ok, is_err, is_ok_and, is_err_and)
  13. Asides - (Functions that return Result in std, Eager vs Laziness)
  14. Summary - (Cheatsheet, Feedback from the review lounge)

Now I know what you’re thinking:

Fourteen posts? You’ve got to be kidding me!

I know it’s a lot of posts. I’ve tried to make each as small as possible with a single focus. I’ve added examples and some diagrams to make it more palatable.

Also don’t feel the need to read the full series at one go. Read as much as you want or choose a topic you want to know more about or are currently struggling with and start there. Be sure to try some of the examples out and experiment with your own changes; That’s the best way to learn.

Jump in at What is a Result?

https://blog.ssanj.net/posts/2024-01-24-working-with-rust-result.html
Extensions