There are open ideas for how to handle “view types” that express that you’re only borrowing specific fields of a struct, including Self, but they’re an ergonomic improvement, not a semantic power improvement.
There are open ideas for how to handle “view types” that express that you’re only borrowing specific fields of a struct, including Self, but they’re an ergonomic improvement, not a semantic power improvement.
Right, and even more to the point, there's another important property of Rust at play here: a function's signature should be the only thing necessary to typecheck the program; changes in the body of a function should not cause a caller to fail. This is why you can't infer types in function signatures and a variety of other restrictions.
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?
I would personally consider null in Java to be an exception to this.
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.
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.
For example, this won't compile:
struct Something { z: usize }
struct Foo<'a> { x: usize, y: &'a Something }
impl<'a> Foo<'a> {
fn bar(&mut self) -> &Something
{ let something = self.bar(); self.x += something.z; something }
}
But if you could tell the borrow checker the mutable borrow of self can never modify z, then it would be safe. This would achieve that: struct Something { z: usize }
struct Foo<'a> { x: usize, y: &'a const Something }
impl<'a> Foo<'a> {
fn bar(&mut self) -> &const Something
{ let something = self.bar(); self.x += something.z; something }
}
I've now had several instances where they would have let me win a battle with the borrow checker succinctly rather than the long work around I was forced to adopt. Const struct members allow you implement read only fields with having to hide them, and provide getters is icing on the cake.