Generics - static types
Swift | Rust |
---|---|
supported Details: docs.swift.org | supported Details: doc.rust-lang.org |
Generics are types (classes, values, functions) that have type parameters.
In the context of this article, we will examine generics as they apply to statically-typed code.
In other words, while the algorithms are generic (e.g. comparing values), the actual code that uses them has concrete types (e.g. comparing integers).
To see the dynamically typed equivalent, see “Generics - dynamic types”.
Swift
Generics are supported in all recent versions of Swift. To define a generic algorithm:
// define a generic type
protocol Costume {
var hasBells: Bool { get }
}
// define a generic function
func firstWithBells<C, S>(costumes: S) -> C? where C: Costume, S: Sequence, S.Element == C {
costumes.first(where: { $0.hasBells })
}
The use of such an algorithm is bound during compilation to a concrete type at the point of use:
// define a concrete type conforming to `Costume`
struct GymnastCostume: Costume {
let name: String
let hasBells: Bool
}
// a concrete array of structs `Array<GymnastCostume>`
let gymnastCostumesArray = [
GymnastCostume(name: "A", hasBells: false),
GymnastCostume(name: "B", hasBells: true)
]
// a concrete result `Optional<GymnastCostume>`
let firstGymnastCostume = firstWithBells(costumes: gymnastCostumesArray)
print(firstGymnastCostume) // prints "B"
Opaque types
To avoid typing out long generic type signatures, Swift allows using the some
keyword as a shorthand.
This concept is called an “opaque type”: the type is still concrete in this case, and is inferred during compilation from the context.
Using opaque types is possible in several contexts:
- function result types (SE-0244 - Opaque Result Types, Swift 5.1)
- function parameters (SE-0341 - Opaque Parameter Declarations, Swift 5.7)
- type parameters within types (SE-0328 - Structural opaque result types, Swift 5.7)
The same function as above, with opaque types (Swift 5.7):
func firstWithBells(costumes: some Sequence<some Costume>) -> (some Costume)? {
costumes.first(where: { $0.hasBells })
}
Note: constraining the associated type
Sequence.Element
with type parameter syntaxSequence<Element>
is made possible by SE-0346 (Lightweight same-type requirements for primary associated types, Swift 5.7) and SE-0358 (Primary Associated Types in the Standard Library, in review).
Rust
Generic algorithms are also supported in Rust.
// define a generic type
pub trait Costume {
fn has_bells(&self) -> bool;
}
// define a generic function
fn first_with_bells<'a, I, C>(iter: &'a mut I) -> Option<&'a C>
where I: Iterator<Item = &'a C>, C: Costume {
iter.find(|&c| c.has_bells())
}
And then a concrete implementation:
// define a concrete type
#[derive(Debug)]
struct GymnastCostume {
name: &'static str,
has_bells: bool,
}
// create a trait impl
impl Costume for GymnastCostume {
fn has_bells(&self) -> bool {
self.has_bells
}
}
// use it
fn main() {
let gymnast_costumes_vec = vec![
GymnastCostume { name: "A", has_bells: false },
GymnastCostume { name: "B", has_bells: true },
];
let mut iter = gymnast_costumes_vec.iter();
let first_gymnast_costume = first_with_bells(&mut iter);
println!("{:?}", first_gymnast_costume); // prints "B"
}
‘impl Trait’
Like Swift, Rust has a notion of unnamed trait-bound types.
These can be used as anonymous type parameters:
// accept all concrete types that implement `Trait`
fn foo(arg: impl Trait) {
}
And as abstract return types:
fn foo() -> impl Trait {
// return some concrete type that implements `Trait`
}