It indeed does. The only difference is that Rust has traits (similar to C++'s concepts) which require explicit mention of what interface the type parameters have inside the function, whereas C++'s templates will have a compile error after instantiation if you passed something that didn't meet the expected contract. This is closer to Rust's macros in operation.
Given
fn foo<T>(a: T, b: T) -> T { a + b }
The compiler will complain that you should have been explicit on how T is going to be used:
error[E0369]: cannot add `T` to `T`
--> src/lib.rs:1:32
|
1 | fn foo<T>(a: T, b: T) -> T { a + b }
| - ^ - T
| |
| T
|
help: consider restricting type parameter `T`
|
1 | fn foo<T: Add<Output = T>>(a: T, b: T) -> T { a + b }
| +++++++++++++++++
whereas in C++ this would have been accepted until you called foo with two things that couldn't be added together, like a Rust macro[1].