DEV Community

ProgramCrafter
ProgramCrafter

Posted on

3 1 1 1 1

Somewhat dynamic typing in Rust - magic of traits

Rust is statically typed language, they say. All the types are known at compilation time, they say.

That's not quite true, and I'd like to demonstrate counterexamples!

Functions working on slices (arrays of unknown size)

fn foo(arr: &mut [usize]) {
    arr[arr.len() - 1] += arr[0];
}

fn main() {
    let mut v: [usize; 4] = [7, 5, 1, 3];
    foo(&mut v);
    println!("{v:?}");    //  [7, 5, 1, 10]
}
Enter fullscreen mode Exit fullscreen mode

As an example, I'll use a function that adds value of first array element to the last one. Note that it takes &mut [usize] - thing called "slice", which doesn't have length indication at all!

If you change length of array v (suitably adding or removing elements in brackets), function foo will be able to process it just as well. Thus, it can be said that foo works on values of different types!

Functions on trait objects

use std::any::Any;

fn foo(something: &dyn Any) -> &'static str {
    if something.is::<char>() {"char"} else {"not-char"}
}

fn main() {
    println!("1: {}", foo(&1));       //  1: not-char
    println!("'k': {}", foo(&'k'));   //  'k': char
}
Enter fullscreen mode Exit fullscreen mode

Any is an interesting Rust trait, implemented by almost all types. It allows you to store references to values of different type in a single container, and then to check if value has a certain type.

Why not all types implement Any?
Any depends on all types having unique IDs. References (and, by extension, types containing references) are bounded by lifetimes, which are hard to put into numbers.

But perhaps the most interesting fact is that you can pass reference to a certain object (for instance, 1 of usize) to foo, it will automatically convert to reference to dyn Any, and the code will compile!

What is '&dyn Any', is there '&Any' as well?
dyn Trait represents an object of some type - unknown at compilation time, but certainly implementing Trait. Those objects may have different sizes and thus cannot be stored normally (passed to function or returned, as well). They must be behind a "smart pointer" - reference or Box, for instance.

There is also impl Trait, representing object of type known at compilation time that implements Trait as well. They may be used without restrictions.

fn maybe_max(a: impl Into<usize>, b: impl Into<usize>) -> usize {
    std::cmp::max(a.into(), b.into())
}
Enter fullscreen mode Exit fullscreen mode

In next post, I'll describe how Rust works with this dynamic typing in more details!

Heroku

Simplify your DevOps and maximize your time.

Since 2007, Heroku has been the go-to platform for developers as it monitors uptime, performance, and infrastructure concerns, allowing you to focus on writing code.

Learn More

Top comments (0)

Hostinger image

Get n8n VPS hosting 3x cheaper than a cloud solution

Get fast, easy, secure n8n VPS hosting from $4.99/mo at Hostinger. Automate any workflow using a pre-installed n8n application and no-code customization.

Start now

👋 Kindness is contagious

Please leave a ❤️ or a friendly comment on this post if you found it helpful!

Okay