It's easier to "abuse" in some languages with casts, and of course borrow checking is not common, but it also seems like just "typed function signatures 101".
Are there common exceptions to this out there, where you can call something that says it takes or returns one type but get back or send something entirely different?
Much analysis is delayed until all templates are instantiated, with famously terrible consequences for error messages, compile times, and tools like IDEs and linters.
By contrast, rust's monomorphization achieves many of the same goals, but is less of a headache to use because once the signature is satisfied, codegen isn't allowed to fail.
That's the whole point of Concepts, though.
Example [0]:
#include <concepts>
template<typename T>
concept fooable = requires(T t) {
{ t.foo() } -> std::same_as<int>;
};
struct only_foo {
int foo();
};
struct foo_and_bar {
int foo();
int bar();
};
template<fooable T>
int do_foo_bar(T t) {
t.bar(); // Compiles despite fooable not specifying the presence of bar()
return t.foo();
}
// Succeeds despite fooable only requiring foo()
template int do_foo_bar<foo_and_bar>(foo_and_bar t);
// Fails even though only_foo satisfies fooable
template int do_foo_bar<only_foo>(only_foo t);
[0]: https://cpp.godbolt.org/z/jh6vMnajjI'd say that's a mistake of the person who wrote the template then.
Also, there are Concepts where you absolutely know which types are allowed, e.g. std::same_as, std::integral, std::floating_point, etc.
The fact that it's possible to make that mistake is basically the point! If "the whole point of concepts" were to "tell you what types you can successfully call it with" then that kind of mistake should not be possible.
It's true that there are certain cases where you know the full set of types you can use, but I'd argue that those are the less interesting/useful cases, anyways.
> Here is the most famous implication of this rule: Rust does not infer function signatures. If it did, changing the body of the function would change its signature. While this is convenient in the small, it has massive ramifications.
Many languages violate this. As another commenter mentioned, C++ templates are one example. Rust even violates it a little - lifetime variance is inferred, not explicitly stated.
I would personally consider null in Java to be an exception to this.
Moreover, this rule is more important for Rust than other languages because Rust makes a lot of constraints visible in function signatures.
But the most important purpose of the rule is communicating that this is a deliberate design decision and a desireable property of code. Unfortunately, there's an overwhelming lack of taste and knowledge when it comes to language design, often coming from the more academic types. The prevailing tasteless idea is that "more is better" and therefore "more type inference is better", so surely full type inference is just better than the "limited" inference Rust does! Bleh.