Why are trait brackets required?

I come from Kotlin, where even something like class MyClass or interface MyInterface is compilable. When you don't need a body for your class, you can omit the curly braces entirely.

I have this specific Rust use case that I came across today:

pub trait ReadRepository<T> {
    fn get_all(&self) -> Vec<T>;
    fn get_by_id(&self, id: i32) -> Option<T>;
}

pub trait CreateRepository<T> {
    fn create(&self, data: T) -> T;
}

pub trait UpdateRepository<T> {
    fn update(&self, id: i32, data: T) -> Option<T>;
}

pub trait DeleteRepository<T> {
    fn delete(&self, id: i32) -> Option<T>;
}

pub trait CRUDRepository<T>:
    ReadRepository<T> + CreateRepository<T> + UpdateRepository<T> + DeleteRepository<T>
{
}

As you can see, the CRUDRepository is just a trait that combines all the other traits above, no need for a body. However, the ugly empty curly braces need to be there for some reason...

Probably because Rust is pretty consistent at requiring a block wherever one is expected (control flow, functions, enums, impls, etc all require a block even if the content is empty or a one-liner). Structs are an exception because struct Foo; is in fact different from struct Foo {}!

3 Likes

Note that CRUDRepository<T> is a distinct trait and may or may not be implemented for types that impl all of the supertraits.

Trait aliases are an unstable feature, allowing you to write

trait CrudRepository<T> =
    ReadRepository<T> + CreateRepository<T> + UpdateRepository<T> + DeleteRepository<T>;

which is not a separately implable trait but merely just sugar for writing the full specified bound out.

6 Likes