Trait bounds constrain generic type parameters so a function can call trait methods on values of unknown type. The syntax <T: TraitName> means "this function works for any type T that implements TraitName".
// Trait bounds: función genérica que acepta cualquier tipo con Display trait Display { fn to_string(self) -> String } struct Point { x: int, y: int } impl Display for Point { fn to_string(self) -> String { return "(" + int_to_string(self.x) + ", " + int_to_string(self.y) + ")" } } struct Circle { radius: int } impl Display for Circle { fn to_string(self) -> String { return "Circulo{radio=" + int_to_string(self.radius) + "}" } } fn print_it<T: Display>(x: T) -> String { return x.to_string() } fn show_twice<T: Display>(x: T) { let s: String = x.to_string() print(s) print(s) } fn main() -> int { let p: Point = Point { x: 7, y: 2 } print(print_it<Point>(p)) let c: Circle = Circle { radius: 10 } print(print_it<Circle>(c)) show_twice<Point>(p) return 0 }
(7, 2)
Circulo{radio=10}
(7, 2)
(7, 2)How it works
print_it<T: Display> is a generic function that accepts any type T as long as it implements Display. The bound T: Display tells the compiler two things: it may generate a specialized version for each concrete type used at the call site (monomorphization), and it guarantees that x.to_string() is always a valid call inside the function body.
show_twice demonstrates that trait-bounded generics are ordinary functions — they can hold local variables, call the trait method multiple times, and produce any side effects. The compiler produces separate machine-code versions for Point and any other type you pass, so there is zero virtual dispatch overhead.
At the call site, the type argument is written explicitly as print_it<Point>(p). Nyx can often infer the type argument, but being explicit is always valid and sometimes clearer when reading code.