The following program might no longer compile if `f` could be overloaded, additional type annotations would be required.
fn f(n: u8) -> u8 {
n + 5
}
let o = Some(Default::default());
println!("{:?}", o.map(f));
In general I would expect overloading to make type inference less reliable (not so much with the compiler getting it wrong, but needing more annotations), which users of Rust that do not want overloading would be right to object to.I'd argue you could not implement overloading without enough support being in place to let the current single signature inference work as it does, which is also probably a good chunk of why its never been really proposed in earnest. Its a hard problem to approach.
> Those kinds of complexities aren't generally something for users to explicitly learn in rote memorization to use the language - if Rust introduced overloading and default arguments all those who don't know it exists won't have the language made any harder to use, but those that want it can take advantage of it.
This however is only true if you never have to work with someone else's codebase, When you start creating dialects your usual 'style' will feel alien in another project or you may have trouble understanding how something works due to an arcane language feature you've never had to use.
There are a lot of best practices that can be quite hard to follow without language support, and would generate too many false positives to warn about. For instance, use after free. There is no lifetime information available in the type system that the C compiler could use to tell you whether you are doing something that could result in a use-after-free. How do you propose the compiler could catch these, except in the most trivial cases?
I think that's pretty terrible advice for something that affects memory safety, and it invalidates your entire "the compiler will warn you" argument. There's a reason Rust has ownership and lifetime annotations: there are many things that a compiler simply cannot infer.
I wouldn't say it's terrible advice though. If I see that something I do is potentially dangerous and would require special care to get it right, I'd first look if there is another way to do it.
References are always guaranteed to point to something that lives longer than them. What they refer to could either be on the stack, or it can be allocated and deallocated on the heap via smart pointers. Box<T> is similar to C++'s unique_ptr; there is a single owner, and when that owner is dropped, the referenced value is dropped. Arc<T> is similar to C++'s shared_ptr; it's an atomically reference counted pointer, so the value will be deallocated when the last clone is deleted.
These combine in ways that allow you to use most of those "best practice" patterns from C and C++, but with actual language guarantees that you will follow them and the compiler will catch it if you don't. In the internals of data structure implementation, you may need to go beyond what this allows with use of unsafe, but you can limit that just to a few small critical pieces of code that can be more thoroughly audited and tested.
In a large project "try to avoid doing that" doesn't really fly; in a large codebase, with dozens of developers, developed over multiple years, people are going to make mistakes. They will need to do some kind of refactoring, and one of the guarantees will get missed.
If you've done a decent amount of work in a large C or C++ codebase, you might appreciate this description of the process of storing a reference to a slice of a Vec in Rust, without having to manually reason about all of the places in which that slice could be invalidated: https://manishearth.github.io/blog/2015/05/03/where-rust-rea....
This article, written as a way to help introduce Rust 1.0, provides a few more examples, though they are artificial rather than from a real-world project: https://blog.rust-lang.org/2015/04/10/Fearless-Concurrency.h...
I think it's bad advice because you need to know what is potentially dangerous. That alienates newcomers to systems programming (which is why one of Rust's goals is to be able to "hack without fear"). There are also some designs that I'd never try to build without safety checks, because they're notoriously hard to get right. Even knowing the fundamentals of memory safety, without a way of enforcing lifetimes and mutability, writing zero-allocation implementations that share immutably can be daunting.
[0] Cell, RefCell, Mutex, Rc, Arc
What's got people excited is the evolution of these systems which have a rich intersection between rigor and usability (to include ecosystem, tooling, etc.). The ability to statically eliminate classes of error with good (or maybe good enough) ergonomics means these questions of "how frequently does this occur" are not relevant - you can drive such occurrences to zero and doing so should become table stakes. This is a proper engineering mindset.
None of this is to dismiss craftsmanship, which definitely has its place. And before the ability to practically apply rigor, that's all there was. After all, people have been building complex systems for centuries without the benefit of modern mathematical foundations. But none of those are good reasons to eschew rigor when it is within reach and when the systems you develop are big/complex/widely-used enough.
The complexity difference between a computer 46 years ago, almost half a century, when C was first developed and now is incomprehensible. It is reflected in how these languages, fundamentally, are willing to just say "since the computer only runs in 30 cycles a second and we are just doing increment operations if you break the rules it undefined" and the idea of having behavior native to the grammar that would impose kilobytes of size complexity or megacycles of computation would have been absurd.
At the time, of course. Modern C++ doesn't hesitate to generate multiple vtables for a complex class while juggling reference counting smart pointers with streamlined initialization grammars that can take up thousands of words of memory. But because the primitives that must remain stable for backwards compatiblity were built on the most constrained architectures the whole house of cards is thus constrained.
I think Rust has already made a lot of "just get it out there, don't think about the ramifications" decisions in its language design, especially around its grammar. On one hand there is still logic to what glyphs do what and how control flow is laid out - particularly concerning minimizing the number of retraced parses the source needs to go through - but it doesn't change the syntax of lifetimes ('), the use of double colon namespacing (::), or the turbofish but being an unintitive disgusting hack.
In my experience the RFC process though has helped temper that. All those blemishes are predominantly from the origin of the language up through ~2014. Once the ecosystem developed and decisions required months or years of hanging around on the issue tracker to see stabilization Rust mostly stopped grafting on arcane behavior and started reusing its syntax in intuitive ways (like impl trait).
I don't think any of the current crop of outstanding wants really makes the language more complex. A lot of it is just increasing the compliers complexity to handle more generic code, be it in terms of kindedness or by type delegation or inference. Those kinds of complexities aren't generally something for users to explicitly learn in rote memorization to use the language - if Rust introduced overloading and default arguments all those who don't know it exists won't have the language made any harder to use, but those that want it can take advantage of it.
As long as Rust is never tempted into the "a substantial portion of this behavior is undefined if not done explicitly as intended" hole that C and C++ were predestined to fall upon I don't think features make the language harder. The core syntax is there, shouldn't change, and is being made easier and more intuitive, not harder and more complex (NLLs save the day, and the number of explicit lifetimes needed have been culled dramatically in the last two years of development). In just a few glyphs of C++: void* = 0 - there are already all manner of edge case, undefined behavior, syntactic complexity beyound comprehension.
Nobody working on Rust today wants arcane, impossible to process grammar or undefined behavior complexity. The language already mostly lacks it. Nobody in their right mind will add it. Thats never a feature, its the bug Rust fixes that is inherent to the C lineage in ways nothing but a rewrite can fix. It took a decade of pain for Python to change its string type from bytes to unicode - there is absolutely no way to salvage C or any of its descendants. By design. From its fundamentals. All the complexity on top has just been repeated attempts to temper the fundamental incongruities.