Type system in Rust - subtypes without inheritance? pwned?
Tags: rust, tutorial
Published: 2024-03-05 18:05:59

Are there features in Rust type system beyond runtime-determined types of trait objects and ability to transmute them into others? Maybe some types have subtyping relation, which allows to safely cast them to super-s?

> A fair warning: we're delving into The Rustonomicon, called also [The Dark Arts of Unsafe Rust](https://doc.rust-lang.org/nomicon/intro.html).
> 
> > THE KNOWLEDGE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF UNLEASHING INDESCRIBABLE HORRORS THAT SHATTER YOUR PSYCHE AND SET YOUR MIND ADRIFT IN THE UNKNOWABLY INFINITE COSMOS.
> 
> Also, we will be assuming considerable prior knowledge, mostly on topic of references (wide and thin) and their lifetimes.

:crab: There are most definitely sub- and supertypes.

# Cases of subtyping in Rust

```rust
trait Vehicle {}
trait Car : Vehicle {}

struct Chevle;
impl Vehicle for Chevle {}
impl Car for Chevle {}
```

## Trait objects

1. `&Chevle` (read-only reference) may be casted either to `&dyn Car` or to `&dyn Vehicle`. After all, if we have read access to our car, then it's also access to *an unspecified* kind of car or a vehicle.
    {% details Why mutable references can't be casted so? %}
    Let's suppose we have garage-variable that holds Chevle. If we were able to create `&mut dyn Car` reference to it, we'd be able to put Kia there, and after ref lifetime expiry Kia would stand in garage for Chevle!
    {% enddetails %}

2. `&dyn Car` to `&dyn Vehicle`, according to the same rationale (*planned, since that requires [changing vtables in wide pointers](https://dev.to/programcrafter/dynamic-typing-in-rust-inheritance-and-vtables-m0j)*).

## Lifetimes

```rust
fn bar<'a>() -> &'a str {
    let s: &'static str = "dev.to";
    s
}
```

1. References have lifetimes; a reference may have its lifetime shortened.
    {% details Why mutable references can change here? %}
    I will demonstrate this with a sample code.
    ```rust
    fn foo() {
      /* 'x */ {
        let mut i = 1;
        let p: &mut usize = &mut i;  // &'x mut usize
        /* 'y */ {
          let mut j = 4;
          p = &mut j;    // cannot assign twice to immutable variable `p`
        }
        *p += 1;
      }
    }
    fn bar() {
      /* 'x */ {
        let mut i = 1;
        let mut p: &mut usize = &mut i;  // &'x mut usize
        /* 'y */ {
          let mut j = 4;
          p = &mut j;
        }  // `j` does not live long enough, since its borrow `p` is used below 
        *p += 1;
      }
    }
    ```
    {% enddetails %}

## Generic types

That is a complex subject perhaps best explained by [its page in The Rustonomicon](https://doc.rust-lang.org/nomicon/subtyping.html).

> - F<T> is covariant over T if T being a subtype of U implies that F<T> is a subtype of F<U> (subtyping "passes through")
> - F<T> is contravariant over T if T being a subtype of U implies that F<U> is a subtype of F<T>
> - F<T> is invariant over T otherwise (no subtyping relation can be derived)

---

# A Rust safety bug

```rust
static UNIT: &'static &'static () = &&();

fn foo<'a, 'b, T>(_: &'a &'b (), v: &'b T) -> &'a T { v }

fn bad<'a, T>(x: &'a T) -> &'static T {
    let f: fn(_, &'a T) -> &'static T = foo;
    f(UNIT, x)
}
```

[There](https://github.com/rust-lang/rust/issues/25860)'s a code that promotes reference having any lifetime to `'static` - one outliving the program, thus valid for its entire run - without memory allocation. You may notice that there's an ignored parameter creating unchecked assertion that `'b` outlives `'a`, which is the root of this issue. There are quite a few variants how to do this, so please remain aware (or don't use double references)!