Rust 101 – 3: Memory and ownership

Continuing on Rust programming basics by looking at ownership and memory management, including the stack and the heap: what they are, how they differ, and why you need to care.

For more help on ownership and the stack and the heap, try Chapter 4 of the Rust book.

Series: Language basics, More syntax, Traits and generics, Building applications, Concurrency and parallelism

This section (Language basics): 1: Intro, 2: Language basics, 3: Memory and ownership, 4: Exercises A1

Links:

The course materials for this series are developed by tweede golf. You can find more information at github.com/tweedegolf/101-rs and you can sponsor the work at github.com/sponsors/tweedegolf. They are released under the Creative Commons Attribution Share Alike 4.0 International license.

This series of videos is copyright 2023 Andy Balaam and the tweede golf contributors and is released under the Creative Commons Attribution Share Alike 4.0 International license.

Rust 101 – 2: Language basics

An introduction to the Rust language basics.

Series: Language basics, More syntax, Traits and generics, Building applications, Concurrency and parallelism

This section (Language basics): 1: Intro, 2: Language basics, 3: Memory and ownership, 4: Exercises A1

  • What Rust is and why you might want to learn it
  • Examining a simple program
  • Learning about the types of variable you can have (numbers, strings, tuples, arrays)
  • Introducing control flow with if, for, while and loop
  • Talking about functions and expressions
  • Preparing ourselves for the next video, which is about memory management

If you’d like to learn more about Unicode and character sets, try my video Interesting Characters where I share how surprisingly interesting this whole area is.

Links:

The course materials for this series are developed by tweede golf. You can find more information at github.com/tweedegolf/101-rs and you can sponsor the work at github.com/sponsors/tweedegolf. They are released under the Creative Commons Attribution Share Alike 4.0 International license.

This series of videos is copyright 2023 Andy Balaam and the tweede golf contributors and is released under the Creative Commons Attribution Share Alike 4.0 International license.

Rust 101 – 1: Course intro

Introducing the Rust 101 series and how to install Rust.

Series: Language basics, More syntax, Traits and generics, Building applications, Concurrency and parallelism

This section (Language basics): 1: Intro, 2: Language basics, 3: Memory and ownership, 4: Exercises A1

Rust 101 is a series of videos explaining how to write programs in Rust.

Follow the “Exercises” link to find the other tools you might want to install to follow along.

The course materials for this series are developed by tweede golf. You can find more information at github.com/tweedegolf/101-rs and you can sponsor the work at github.com/sponsors/tweedegolf. They are released under the Creative Commons Attribution Share Alike 4.0 International license.

This series of videos is copyright 2023 Andy Balaam and the tweede golf contributors and is released under the Creative Commons Attribution Share Alike 4.0 International license].

Combining two function types with & (ampersand) in TypeScript (intersection)

Combining interfaces/objects with &

When you combine two types in TypeScript with & (ampersand), it is called an Intersection Type.

For example:

interface Particle {
    mass: number;
}

interface Wave {
    wavelength: number;
}

type Both = Particle & Wave;

The new type Both is both a particle and a wave, so it has both properties mass and wavelength.

If you combined the types with |, making a Union Type, like this:

type Either = Particle | Wave;

then the new type Either would be either a particle or a wave, not both. To use it, you would need to find out which it was, before accessing only the property (mass or wavelength) that you now know exists, and not the other one.

Combining functions with &

This is all fine, and relatively easy to understand, but what about when you combine function types in this way?

type TakesString = (s: string) => void;
type TakesNumber = (n: number) => void;
type TakesStringOrNumber = TakesString & TakesNumber;

Now you have a type that is both TakesString AND TakesNumber, which means a function that can do both things, which means it takes a string or a number as an argument. I can create something that has this type like this:

const f: TakesStringOrNumber = (x: string | number) => {console.log(x);};

Notice how that | symbol sneaked in there? In order to satisfy the & on the function types, the argument types use |. When two things have this kind of opposite relationship it’s sometimes known as contravariance.

It has to be this way: for the function to be TakesString AND TakesNumber the argument needs to be string OR number.

Functions with literal types

If your functions take Literal Types this can get even more confusing:

type TakesTrue = (success: true) => void;
type TakesFalse = (success: false) => void;

Any argument you pass to a function that is TakesTrue must be true. Similarly, to call a function that is TakesFalse you must pass in false. TypeScript lets you do this, which is fun.

So now imagine you combine these types:

type TakesEither = TakesTrue & TakesFalse;

Now, TakesEither is both TakesTrue and TakesFalse so it can take in either true or false.

Let’s make a function that can do that:

const f: TakesEither = (success: boolean) => { console.log(`${success}`); };

This works – f takes a boolean, so it does indeed allow you to pass in true or false, as required to be a TakesEither.

What’s weird though, is that you can’t do this:

let x: boolean = new Date().getHours > 12;
f(x); // Compile error

(Ignore the date stuff – x is just a boolean that might be true or false.)

Here is the compile error:

index.ts:9:7 - error TS2769: No overload matches this call.
  Overload 1 of 2, '(success: true): void', gave the following error.
    Argument of type 'boolean' is not assignable to parameter of type 'true'.
  Overload 2 of 2, '(success: false): void', gave the following error.
    Argument of type 'boolean' is not assignable to parameter of type 'false'.

The thing is that the compiler only knows that f is a TakesEither, which means that it is either a function that takes true or a function that takes false. It doesn’t know that f can take a boolean (even though here it can!).

This code does work:

let x: boolean = new Date().getHours() > 12;
if (x) {
    f(x); // OK
} else {
    f(x); // OK
}

Why? Because inside the first part of the if, the compiler knows that x is true, so it can call f with it, because f can take true as an argument. Similarly in the second half, it knows x is false.

In future, the compiler might grow the ability to handle work in the first case, but at time of writing, we see this error.

Cool, huh?

(Thanks to jcalz for this stackoverflow answer that helped me understand this a little: typescript intersection types of function.)

Accessing services on the host from a Docker container or a Podman one

I use Podman to provide Docker-like stuff on my dev machine without effectively being root.

Talking to the container host in Docker

There is a little trick for accessing HTTP services on the container host in Docker: you add --add-host host.docker.internal:host-gateway to the command line when you run the docker container, for example:

docker run -d --add-host host.docker.internal:host-gateway my-container:latest

Now in your code you can refer to the Docker container host using the name host.docker.internal.

Talking to the container host in Podman

However, this doesn’t work in Podman. Instead, you need to add --network slirp4netns:allow_host_loopback=true to the command line, for example:

docker run -d --network slirp4netns:allow_host_loopback=true my-container:latest

Now in your code you can refer to the Podman container host using the name host.containers.internal.

Supporting both

For this to work in both Docker and Podman, your container needs to know which framework it is working inside, and use either host.docker.internal or host.containers.internal explicitly, which is a pity.