Skip to main content

📝 Notes

Things I want to remember.

2023

#learn-rust What is the question mark (?) operator?

· loading ·

The question mark operator provides an alternative and concise approach to handle errors in functions that return the Result or Option type. By using the ? operator, we can reduce the amount of code needed to handle returning Err or None from Result<T, Err> or Option<T> types.

Here’s a quick example:

Instead of using a match statement to check the success of a function do_something() that returns a Result<T, Err> type, we can achieve the same result with the following shorter syntax:

let a = match do_something() {
  Ok(a) => a,
  Err(e) => return Err(e),
}

While this is quite expressive, it can be shortened to:

let a = do_something()?;

This operator unwraps valid values and propagates erroneous values to the calling function.

Now, the question arises: should we always use ?? Well, as is often the case, it depends.

The ? operator simplifies code and enhances readability by reducing boilerplate. However, it can only be used in functions that return Result or Option types. Therefore, changing the return type of a function is necessary to utilize this operator, which may not always be feasible or desirable. Furthermore, the ? operator is not available in main() functions.

Additionally, while the ? operator is thorough and concise, it lacks the ability to provide custom behavior. There might be situations where you prefer to:

  • Invoke custom behavior, such as error recovery
  • Create a custom error
  • Even panic

In such cases, the match statement is still required to fulfill these specific needs.

Let’s consider another example that demonstrates error propagation across multiple function calls:

use std::io;

fn get_user_age() -> Result<u8, io::Error> {
    // Simulating an error condition
    Err(io::Error::new(std::io::ErrorKind::Other, "could not get age!"))
}

fn get_user_name() -> Result<String, io::Error> {
    // Simulating a successful operation
    Ok("Alice".to_string())
}

fn process_user_data() -> Result<(), io::Error> {
    let _ = get_user_name()?;
    let _ = get_user_age()?;
  
    // Processing user data...
    Ok(())
}

fn main() {
    match process_user_data() {
        Ok(()) => println!("User data processed successfully"),
        Err(e) => println!("Error: {}", e),
    }
}

Since get_user_age() encounters an error, the program will terminate and display the following message:

Error: could not get age!

Well, this is all I know about the ? operator. I hope you found this useful.

References



Justfile with polyglot support

· loading ·

I just discovered that the just command runner supports running commands in multiple languages! As I will probably forget this, I will write it down here.

py:
  #!/usr/bin/env python3
  name = "Python"
  print(f'Greetings from {name}!')

js:
  #!/usr/bin/env node
  const name = "JavaScript";
  console.log(`Greetings from ${name}!`)

go:
  #!/Users/c.voigt/go/bin/gorun
  package main

  import "fmt"

  func main() {
      var name = "Go"
      fmt.Printf("Greetings from %s!\n", name)
  }

sh:
  #!/usr/bin/env sh
  name="Shell"
  echo "Greetings from ${name}!"

rhai:
  #!/Users/c.voigt/.cargo/bin/rhai-run

  let answer = "Rhai";
  print(`Greetings from ${answer}`);

To run go code I installed gorun. Admittedly this is a bit hacky, but certainly does the job.

To use Rhai install it with cargo: cargo install rhai and check which rhai-run to get the path to the executable.



#learn-rust: Rust enums

· loading ·

Defining an enum is not very spectacular

enum Color {
  Yellow,
  Red,
  Green,
  Blue,
}

Let’s pattern-match against the enum, forgetting to match one of the variants. The compiler will warn us about this.

fn print_color(color: Color) {
  match color {
    Color::Yellow => println!("Yellow"),
    Color::Red => println!("Red"),
    Color::Green => println!("Green"),
    // Color::Blue => println!("Blue"),
  }
}

Attaching methods to enums is also possible 🤯

impl Color {
  fn green_part(&self) -> bool {
    match self {
      Color::Yellow => true,
      Color::Blue => true,
      _ => false,
    }
  }

  fn is_green(&self) -> bool {
    // pattern matching self on the if-statement
    if let Color::Green = self {
      return true;
    }
    return false
  }
}


#learn-rust: retrieving optional values

· loading ·

match statement #learn-rust

let fruits = vec!["banana", "apple", "coconut", "orange", "strawberry"];
for &index in [0, 2, 99].iter() {
    match fruits.get(index) {
        // note the "&" to get the Option<&&str>
        Some(&"coconut") => println!("Coconuts are awesome!!!"),
        Some(fruit_name) => println!("It's a delicious {}!", fruit_name),
        None => println!("There is no fruit! :("),
    }
}

Whenever you use the match expression, keep the following rules in mind:

  • match arms are evaluated from top to bottom. Specific cases must be defined earlier than generic cases or they’ll never be matched and evaluated.
  • match arms must cover every possible value that the input type could have. You’ll get a compiler error if you try to match against a non-exhaustive pattern list.

Use pattern matching for convenience
#

If we expect a specific Some value in an Option, we could use match (and drop anything else):

let a_number: Option = Some(7);
match a_number {
    Some(7) => println!("That's my lucky number!"),
    _ => {},
}

Or use a shorter, equivalent version:

let a_number: Option = Some(7);
if let Some(7) = a_number {
    println!("That's my lucky number!");
}

Use unwrap and expect carefully
#

unwrap and expect can be used, but will panic if Option is None

let gift = Some("candy");
assert_eq!(gift.unwrap(), "candy");

let empty_gift: Option<&str> = None;
assert_eq!(empty_gift.unwrap(), "candy"); // This will panic!

This will panic like this:

thread 'main' panicked at 'called `Option::unwrap()` on a `None` value', src/main.rs:6:27

With expect we can set a custom panic message:

let a = Some("value");
assert_eq!(a.expect("fruits are healthy"), "value");

let b: Option<&str> = None;
b.expect("fruits are healthy"); // panics with `fruits are healthy`
thread 'main' panicked at 'fruits are healthy', src/main.rs:6:7

As both, unwrap and expect can panic it is recommended to avoid them and use either of following options instead:

  1. Use pattern matching and handle the None case explicitly.
  2. Call similar non-panicking methods, such as unwrap_or, which returns a default value if the variant is None or the inner value if the variant is Some(value).
assert_eq!(Some("dog").unwrap_or("cat"), "dog");
assert_eq!(None.unwrap_or("cat"), "cat");


#learn-rust: generics

· loading ·

Syntax of a generic struct:

struct Point {
    x: T,
    y: T,
}

fn main() {
    let boolean = Point { x: true, y: false };
    let integer = Point { x: 1, y: 9 };
    let float = Point { x: 1.7, y: 4.3 };
    let string_slice = Point { x: "high", y: "low" };
}

Note, that multiple different types are possible:

struct Point {
    x: T,
    y: U,
}

fn main() {
    let integer_and_boolean = Point { x: 5, y: false };
    let float_and_string = Point { x: 1.0, y: "hey" };
    let integer_and_float = Point { x: 5, y: 4.0 };
    let both_integer = Point { x: 10, y: 30 };
    let both_boolean = Point { x: true, y: true };
}


#learn-rust: tests

· loading ·

Syntax to write tests in Rust:

fn add(a: i32, b: i32) -> i32 {
    a + b
}

#[cfg(test)]
mod add_function_tests {
    use super::*;

    #[test]
    fn add_works() {
        assert_eq!(add(1, 2), 3);
        assert_eq!(add(10, 12), 22);
        assert_eq!(add(5, -2), 3);
    }

    #[test]
    #[should_panic]
    fn add_fails() {
        assert_eq!(add(2, 2), 7);
    }

    #[test]
    #[ignore]
    fn add_negatives() {
        assert_eq!(add(-2, -2), -4)
    }
}

The cfg attribute controls conditional compilation and will only compile the thing it’s attached to if the predicate is true. The test compilation flag is issued automatically by Cargo whenever we execute the command $ cargo test, so it will always be true when we run our tests. The use super::*; declaration is necessary for the code inside the add_function_tests module to access the add in the outer module.



2022

unable to find zig installation directory: FileNotFound

· loading ·

If zig refused to build because it is “unable to find zig installation directory”, you need to ensure the lib directory resides at the same relative path as the zig binary.

I played around and wanted to create a container image to streamline my Zig build experience. Doing so, I used the official tarball and copied over the zig binary, put it into /usr/local/bin, and assumed this was going to work. zig version even proved me correct:

$ zig version
0.10.0-dev.2431+0e6285c8f

However, once I wanted to build, I got this error message:

error: unable to find zig installation directory '/usr/local/bin/zig': FileNotFound

As usual, Zig error messages aren’t that helpful (yet). Only coincidentally, I found an old dump of zigs freenode IRC channel pointing me in the right direction:

21:25 the zig binary needs to stay at the same relative path to the lib dir

So the solution is not only to copy the binary but also the lib directory from the tarball.

Hopefully, this will be picked up by the search engines, so next time someone encounters the issue, it’s a little bit less of a riddle.



2019

Following logs of multiple Kubernetes Pods

· loading ·

Following your logs is a crucial part of application and operation debugging. tail -f is every sysadmins best friend - easy, fast, and grepable!

While centralized logging solutions such as ELK/EFK, Splunk or Grafana Loki are almost common sense, they are some times overkill. Also, they are not accessible from the command line - which is usually the center of your debugging operations.

To stream logs of Kubernetes workloads we are already used to:

$ kubctl -n <namespace> logs -f <pod-id>

But what if you want to read log interactions of different workloads? What if you want to see the logs of all replicas of that same pod?

For this purpose Kubernetes allows log streaming based on label selectors:

$ kubectl -n namespace logs -l app=myapp

This will stream logs of all Pods with the label app=myapp to your console.

As already mentioned, this does not only affect pods of the same type but all pods sharing the same label. This might include Replicasets and Jobs as well!

Of course, greping and other magic is possible as well ❤.