Monday, 14 September 2026

Traits in Rust - Adding behavior to types in Rust

In C#, we have extension methods to add behavior and functionality to existing types. This is done without changing the existing type definition. In Rust, we have traits to add behavior. In Rust, traits are core language feature and a more fundamental abstraction mechanism. It is closer to a mix of interfaces, generic contraints and extension methods. Let's look at how we can add behavior in Rust using traits. I have added the source code in this article in my Github repo here:

➡️ https://github.com/toreaurstadboss/RustLinqDemo1

The API-docs for the traits methods implemented in my small hobby lib of Linq-like Rust traits with also source code is available in the following

RustDoc



➡️ https://toreaurstadboss.github.io/RustLinqDemo1/docs/doc/rustlinqdemo1/sequence_extensions/index.html

Let's look at making some methods similar to C# and the extension methods of LINQ as a good example how to use traits in Rust. We are going to use either a slice type , which is written as [T] or a vector. This will be similar to code that uses to IEnumerable of T in C#. Let's first define a Skip method and a Take method using traits.

sequence_extensions.rs



/// Returns an owned slice of the start of a vector.
/// Provides owned start of the provided vector.
pub trait TakeOwned {
    type Item;

    /// Returns up to n items from the start of the vector.
    fn take_owned(self, n: usize) -> Vec<Self::Item>;
}

impl<T> TakeOwned for Vec<T> {
    type Item = T;

    fn take_owned(self, n: usize) -> Vec<Self::Item> {
        let len = self.len();
        self.into_iter().take(n.min(len)).collect()
    }
}

// Provides skipped slice by n items that returns the rest of the vector
pub trait SkipOwned {
    type Item;

    fn skip_owned(self, n: usize) -> Vec<Self::Item>;
}

impl<T> SkipOwned for Vec<T> {
    type Item = T;

    fn skip_owned(self, n: usize) -> Vec<Self::Item> {
        self.into_iter().skip(n).collect()
    }
}


As the code above shows, the trait is first defined, conceptually similar to an interface in C# and the impl bit takes care of implementing the trait. As we see, the trait does only define and not implement the logic. This is done in the impl bit. Our trait here is generic defined by type parameter T and is implemented for Vector of type Self::Item which is bound to T via the type argument. Both skip and take methods here are possible to chain, just like in C# Linq methods, it is shown below how to do this in a Rust unit test below.

tests / test.rs



use rustlinqdemo1::sequence_extensions::{
    All, Any, ElementAtOrDefault, FirstOrDefault, LastOrDefault, SkipOwned, SkipTakeOwned,
    TakeOwned, TakeRef,
}; //a  list of traits shown here

#[test]
fn skip_owned_take_owned_chained_returns_expected() {
    let values : Vec<i32> = vec![1, 2, 3, 4, 5, 6, 7];
    assert_eq!(values.skip_owned(3).take_owned(4), vec![4, 5, 6, 7]);
}


Any method here that returns an owned slice of memory is named _owned as suffix as a Rust convention. And any method that borrows slices of memory are named _ref in my little Rust Linq-like lib. A trait could of course combine multiple functionality , not just for example use chained methods. Let's look at a SkipTakeOwned method.

sequence_extensions.rs



/// Provides owned subsequences by consuming a vector.
pub trait SkipTakeOwned {
    type Item;

    /// Returns up to n items after skipping m items.
    fn skip_take_owned(self, m: usize, n: usize) -> Vec<Self::Item>;
}

impl<T> SkipTakeOwned for Vec<T> {
    type Item = T;

    fn skip_take_owned(self, m: usize, n: usize) -> Vec<Self::Item> {
        let len = self.len();
        self.into_iter().skip(m.min(len)).take(n.min(len)).collect()
    }
}



The test looks like the following

tests / test.rs



#[test]
fn skip_take_owned_returns_requested_range() {
    let values = vec![1, 2, 3, 4, 5];

    assert_eq!(values.skip_take_owned(1, 2), vec![2, 3]);
}


A more complex example of a Rust trait is using additional libs to make a group by method.

sequence_extensions.rs



use std::collections::HashMap;

use itertools::Itertools;

/// Groups items into owned vectors keyed by a selector result.
pub trait GroupByOwned {
    type Item;

    /// Consumes the vector and groups its items by `key_selector`.
    fn group_by_owned<F, K>(self, key_selector: F) -> HashMap<K, Vec<Self::Item>>
    where
        F: FnMut(&Self::Item) -> K,
        K: std::hash::Hash + Eq;
}

impl<T> GroupByOwned for Vec<T> {
    type Item = T;

    fn group_by_owned<F, K>(self, key_selector: F) -> std::collections::HashMap<K, Vec<T>>
    where
        F: FnMut(&T) -> K,
        K: std::hash::Hash + Eq,
    {
        self.into_iter().into_group_map_by(key_selector)
    }
}


To use the itertools, add the following Crate into your `cargo.toml` file:

Cargo.toml



[dependencies]
itertools = "0.15.0"


For C# developers, Crates are library packages, just another package format as Nuget in .NET or Npm in NodeJs. The test below show how to test the GroupByOwned trait implementation in a unit test.

tests / test.rs



#[cfg(test)]
#[derive(Debug, Clone)]
struct User {
    id: i32,
    name: String,
}

#[cfg(test)]
fn sample_users() -> Vec<User> {
    vec![
        User {
            id: 1,
            name: "Alice".to_string(),
        },
        User {
            id: 2,
            name: "Bob".to_string(),
        },
        User {
            id: 3,
            name: "Bob".to_string(),
        },
    ]
}

  #[test]
    fn groupby_returns_expected_count() {
        let users = sample_users();
        let grouped_users = users.group_by_owned(|user| user.name.clone());

        assert_eq!(grouped_users.len(), 2);
        assert_eq!(grouped_users.get("Alice").map(Vec::len), Some(1));
        assert_eq!(grouped_users.get("Bob").map(Vec::len), Some(2));
    }


Sunday, 6 September 2026

Listing all case types of C# 15 Unions

This article presents some C# 15 extension methods to discover the case types of unions in C# 15. This is the Language version coming to .NET 11 in November, 2026 (estimated release date). The code is already possible to test out. You will need to properly test this before .NET 11 ships:
  • NET 11 sdk (preview)
  • Vscode insiders version
  • C# extension in Vscode updated to preview version
  • C# devkit extension in Vscode updated to preview version
Please note that the .csproj must be set to preview Example .csproj :

  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>net11.0</TargetFramework>
    <LangVersion>preview</LangVersion>
    <ImplicitUsings>enable</ImplicitUsings>
    <Nullable>enable</Nullable>
  </PropertyGroup>

The Github repo for the code in this article is here:



C# Unions are coming in C# 15 and .NET 11. It seems like listing up the case types still needs you to use reflection. Probably we will see union types get more features in later C# versions, they are in C# 15 quite a new thing to C# and the language team of C# spent quite a few year to get it to work with the language and CLR. Shown below is an extension method that uses reflection in C# to discover the union case types. It is based that a union always implements the interface IUnion , often implicitly via just using the union keyword - in case it is not a Manual Union. By inspecting the mandatory constructors of the union and retrieving the first parameter's type , we can get the types of the union.

UnionExtensions.cs



/// <summary>
/// Provides reflection-based helpers for C# unions.
/// </summary>
public static class UnionExtensions
{
    /// <summary>
    /// Retrieves the union case types from generic type reference (compile-time checked)
    /// </summary>
    /// <typeparam name="TUnion">The union type to inspect.</typeparam>
    /// <returns>The distinct case types exposed by the union constructors.</returns>
    public static IReadOnlyList<Type> GetCaseTypes<TUnion>() where TUnion : IUnion
    {
        return GetUnionCaseTypes(typeof(TUnion));
    }

    /// <summary>
    /// Retrieves the union case types from a runtime type reference.
    /// </summary>
    /// <param name="t">The union type to inspect.</param>
    /// <returns>The distinct case types exposed by the union constructors.</returns>
    public static IReadOnlyList<Type> GetUnionCaseTypes(this Type t){

            if (t == null || !typeof(IUnion).IsAssignableFrom(t)){
                return Array.Empty<Type>(); //guard against null or non-union types, just return an empty result in this case
            }
  
            return t
                .GetConstructors(System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Instance)
                .Where(constructor => constructor.GetParameters().Length == 1)
                .Select(constructor => constructor.GetParameters()[0].ParameterType)
                .Distinct()
                .ToArray();
    }
   
}


The method below does not throw an exception in case the type t is null or a type that is not assignable from IUnion, i..e not a type that is a union type. Instead it just returns an empty array. Although .NET 11 is expected to be shipped in November this year (2026), it does not seem like an easy way to get the case types of a union can be easily done without such a helper method, there will probably not be a language specific way of retrieving the case types of the union. Below is sample code demonstrating the usage of the union case type listing helper extension method shown above. First off, defining a union type that has four records and also common behavior, adding a description expression bodied method.


public record Dog(string Name);
public record Cat(int NumberOfLives, string? Name = default);
public record Parrot(bool WantsCrackers, string? Name = default);
public record GoldFish(bool MakesBubbles, string? Name = default);


union Pet(Dog, Cat, Parrot, GoldFish)
{

    public string Description => this switch
    {
        Cat cat => $"{cat.Name} says: Meow! I got {cat.NumberOfLives} lives left 🐈",
        Dog dog => $"Bark Bark! {dog.Name} says! 🦴 πŸ•",
        Parrot parrot => $"🦜Squawk! {parrot.Name} says: {(parrot.WantsCrackers ? "I want crackers!" : "Give us a kiss!")}",
        GoldFish goldFish => $"🐠 {goldFish.Name} says: Blub Blub! {(goldFish.MakesBubbles ? "I make bubbles!" : "I don't make bubbles!")}"
    };
}


The demo code below then makes use of the Pet union type defined and using the extension helper method. Please note the usage of the method that uses the parameterless method GetCaseTypes that is static compliler checked via the passed in type argument and the use of the GetUnionCaseTypes, that is runtime based where the type is passed in to the method. As shown in the GetUnionCaseTypes, erroneous usage is guarded. In case a wrong type is passed in and is not a union type which implements IUnion, an empty array is returned.


using System.Runtime.CompilerServices;

/// <summary>
/// Demonstrates discovering the case types of a C# union.
/// </summary>
public class Union1Demo
{

    /// <summary>
    /// Runs the union case discovery and pattern-matching examples.
    /// </summary>
    public static void RunDemo()
    {
        Console.WriteLine($"Pet union cases: {string.Join(", ", UnionExtensions.GetCaseTypes<Pet>().Select(type => type.Name))}");

        var somePets = new Pet[]{
            new Dog("Rex"),
            new Cat(7, "Whiskers"),
            new Parrot(true, "Polly"),
            new GoldFish(true, "Timmy")
        };

        foreach (var pet in somePets)
        {
            Console.WriteLine(pet.Description);    
        }

        Console.WriteLine();
        Console.WriteLine("Listing all pet union case types:\n-----------------------------------------");
        foreach (var caseType in typeof(Pet).GetUnionCaseTypes())
        {
            Console.Write($"* {caseType.FullName}");
            Console.WriteLine($" with props: {string.Join(", ", caseType.GetProperties(System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.Public).Select(p => p.Name))}");
        }

    }

}


The output of running the demo code is shown below:


Pet union cases: Dog, Cat, Parrot, GoldFish
Bark Bark! Rex says! 🦴 πŸ•
Whiskers says: Meow! I got 7 lives left 🐈
🦜Squawk! Polly says: I want crackers!
🐠 Timmy says: Blub Blub! I make bubbles!

Listing all pet union case types:
--------------------------------
* Dog with props: Name
* Cat with props: NumberOfLives, Name
* Parrot with props: WantsCrackers, Name
* GoldFish with props: MakesBubbles, Name


Sunday, 16 August 2026

Rust - Introduction to lambda style closures

Rust Language Primer - FirstOrDefault with Lambda style closures

If you're a C# developer exploring Rust, you’ll quickly notice that many concepts feel familiar — especially when working with collections, generics, and filtering values using lambda‑like syntax. In C#, you might write:

v.FirstOrDefault(x => x % 2 == 0);

I Rust, this is similar, note that we do not use extension methods like in C# and LINQ.

|x| x % 2 == 0

This article walks through how to build Rust equivalents of FirstOrDefault, including default values, nullable handling, and predicate‑based filtering. Along the way, you’ll see how Rust’s Option<T>, generics, and closures map nicely to concepts you already know from C#.

🧠 Rust Closures: The |x| Syntax

Rust closures use vertical bars to define parameters: C#: x => x % 2 == 0 Rust: |x| x % 2 == 0 |x| which for many who are familiar to some entry level Calculus reads like 'absolute' value - this is instead the 'x goes to' operator in Rust! Closures in Rust are strongly typed, safe, and have zero runtime overhead thanks to monomorphization.

πŸ› ️ Implementing FirstOrDefault in Rust

The code shown in this article will show three variants
  • 1. A default‑based version (T::default())
  • 2. A nullable version using Option<T>
  • 3. A predicate‑based version similar to C# lambdas
  • Information: Each method is shown first, followed by usage examples.
πŸ“Œ First variant - 1. first_or_default — Using T::default() This version returns the first element or a default value (like 0 for i32). It maps good to types like non-nullable integers and other numeric numbers as mentioned i32 but many other scenarios too.

/// Returns the first element of the slice, or T::default() if the slice is empty.
fn first_or_default<T: Default + Clone>(slice: &[T]) -> T {
	slice.first().cloned().unwrap_or_default()
}

πŸ“Œ Second variant - 2. first_or_defaultv2 — Nullable Version Using Option<T> Rust does not have nulls — instead, it uses Option<T>. (Similar to Monad Option seen in Functional Programming)

/// Returns the first element wrapped in Option<T>, or None if empty.
fn first_or_defaultv2<T: Clone>(slice: &[T]) -> Option<T> {
	slice.first().cloned()   //notice the simplified syntax where T does not have to include Default trait and the omitting of unwrap_or_default()
}

This is the closest Rust equivalent to nullable types in C#. πŸ“Œ Third variant 3. first_or_default_where — Predicate‑Based Filtering Let's look at adding a predicate (filter) capability too for FirstOrDefault method. This is the Rust equivalent of: v.FirstOrDefault(x => x % 2 == 0);

/// Returns the first element matching the predicate, or None if none match.
fn first_or_default_where<T, F>(slice: &[T], predicate: F) -> Option<T>
where
T: Clone,
F: Fn(&T) -> bool,
{
    if slice.is_empty() {
        return None;
    }
	slice.iter().find(|x| predicate(x)).cloned()   // |x| is to be read as 'x goes to' similar to C# 'x => '
}

Notice the closure syntax: |x| predicate(x) — just like C# lambdas, but with pipes instead of parentheses.

πŸ§ͺ Example Usage

Below is a complete example showing how these functions behave with vectors, empty slices, nullable values, and predicate filtering.

fn main() {

// A normal vector of i32 values
let v = vec![1, 2, 3];

// Uses T::default() (0 for i32) when the slice is empty
let first_number_nonempty_vector = first_or_default(&v);
println!("First number: {:?}", first_number_nonempty_vector);

// Explicitly typed empty vector
let empty: Vec<i32> = vec![];
let first_number_empty_vector = first_or_default(&empty);
println!("First number of empty vector: {:?} {:?}", empty, first_number_empty_vector);

// Nullable version: returns Option<T>
let first_number_empty_vector_nullable = first_or_defaultv2(&empty);
println!(
"First number (nullable) of empty vector {:?}: {:?}  . - Is the result None? : {}",
empty,
first_number_empty_vector_nullable,
first_number_empty_vector_nullable == None
);

// Another vector for predicate-based filtering
let s = vec![11, 3, 7, 13, 128, 5];

// Finds the first even number using a closure (lambda)
let first_even_number_nonempty_vector = first_or_default_where(&s, |x| x % 2 == 0);
println!("First event number of non-empty vector {:?}: {:?}", s, first_even_number_nonempty_vector);

// unwrap_or_else provides a safe fallback (0) if None
println!(
"First event number of non-empty vector {:?}: {:?}",
s,
first_even_number_nonempty_vector.unwrap_or_else(|| 0)
);
}

πŸ“€ Output

First number: 1
First number of empty vector: [] 0
First number (nullable) of empty vector []: None  . - Is the result None? : true
First event number of non-empty vector [11, 3, 7, 13, 128, 5]: Some(128)
First event number of non-empty vector [11, 3, 7, 13, 128, 5]: 128



🎯 Final Thoughts

Rust may look different at first, but if you're coming from C#, you’ll quickly recognize familiar patterns:
  • Option<T> behaves like nullable types
  • |x| closures behave like C# lambdas
  • Generics are powerful and zero‑cost
  • unwrap_or_default() and unwrap_or_else() feel like C#’s ?? operator or GetValueOrDefault()
  • Rust gives you the same expressive power — but with stronger safety guarantees and no runtime overhead.
If you're a C# developer exploring Rust, trying to implement C# Linq methods is a good start to familiarize yourself with the syntax. Happy (Rust) coding! πŸ¦€