One could argue that with AI writing so much code, the ease with which humans can read & understand it (for review purposes) is even more important than before.
Exactly, that's all his PR had to be. The history of finding the issue could be an interesting story (I bet it involves Elixir!), but in places it reads as almost malicious. If I received a PR anything like that on something I maintained, it would be received very poorly. The author comes off as overly aggressive toward the maintainers and far too sensitive to their response.
I remember the "History of English Podcast" covering a lot of this. I'm more a programming language nerd than spoken language, but I still found it fascinating.
Old English was a Germanic language, later heavily influenced by Norman/French vocabulary. French of course descended from Latin, and Latin and Germanic languages both belong to the Indo-European family of languages. (The "C" language of humanity, if you will.)
French was forcibly thrust on the population in 1066, but of course the conquerors were the elite, and the defeated, their servants. So if you tend a cow, you call it with the Germanic word: cow, not vache. But if you consume its expensive meat, you name it in French: boef / beef, not rind(fleisch).
Zed is the only GUI editor I've used as my daily driver after 15+ years of development (hardcore vim fan). Vim Mode is wonderful, the terminal pane works well, UI is butter-smooth, LSP integration beats VS Code's IMHO, and the LLM-powered autocomplete works well (haven't tried chat/generation). Nice themes, too.
The lack of debugger support will put some off, but I bet they'll get to it someday. It's already progressed so much in the four or five months I've used it (esp. Vim Mode).
Right? I seem to be the only high-level SWE at my company who hates RuboCop. Some of it's fine (e.g. no globals, use x.count { ... } instead of x.select { ... }.count). But so many of the rules are obsessed with trivial details like the "right" style of blocks to use (do vs {}) or "" vs '' for strings. Pointless busy work. I think the dumbest is requiring "def (x=true)" over "def (x = true)". Seriously, why could that possibly matter??
I see that as: it doesn't matter, therefore let's do one consistent thing and you'll never get a person saying "this should be x=true". RoboCop forces it one way and I never have to have a syntax/formatting discussion again.
You may think it really really doesn't matter, to the point that whatever form should be accepted. But then, would you accept my "def foo (x =true ,y= false )"? I've seen enough PRs with that kind of code to know that people will submit it one day.
> I think the dumbest is requiring "def (x=true)" over "def (x = true)". Seriously, why could that possibly matter??
For Python, I use ruff to autoreformat my code into a consistent style. The CI pipeline fails the PR if the style is violated it. Fixing it is a one-liner. Actually, if you use VSCode’s ruff extension, a zero-liner, since it auto-formats on save.
For Java, I use Diffplug Spotless for a similar experience.
One advantage of forcing the code to be auto-formatted, is it makes diffs cleaner - you eliminate diffs where someone just changed the code style, since the code style never changes.
Why does it matter when there is auto-fix-on-save? I just write whatever, hit ctrl+s and the code is fixed. The result: The code is "fixed" based on whatever rule and it doesn't cost me a thing. I don't think about it and I don't lift a finger.
But I do personally appreciate the tidiness of code-consistency.
Most of these trivial rubocop rules autoformat so it doesn't bother anyone. The ones that don't are usually odd things like code that doesn't run or things with the same name, which can't be safely autofixed.
this is an override in your project's .rubocop.yml
For me it's a bit opposite, I use and appreciate rubocop for simple formatting, not all of the rules but some of them, where autocorrect works. Recently I reformatted a big old project which was written by pretty sloppy developers who didn't care about whitespace, and it's just easier to work with now.
But I truly hate when rubocop tries to be smart about method calls, amount of lines in methods and amount methods or some other "code smell" metric.
yes, the tool allows to check for it, but it's not THAT smart. I have reasons. I'd happy to explain those reasons on code review to a human, but I'm not that happy to appease a dumb tool.
`””` vs `''` isn’t just a style rule. In Ruby there are performance implications with double quoted strings. Strings in double quotes are interpolated for variable substitution, whereas strings in single quotes are processed literally, no variable replacement overhead. Is it going to matter for most code? Probably not, but good to know nonetheless imo.
I also am not sure why it's intuitive at all. Article doesn't mention why - ruby has a parse phase, it doesn't scan your string byte for byte every time it runs looking for an interpolation. During the parse those "foo#{bar}" are replaced by something like 'foo'.dup << bar.to_str in the bytecode, but that happens only when parser hits #{ in a double quoted string, there's no penalty for that and it happens just once. Only really old, naive interpretators parse source code each time line is hit. (Or some weird ones, where interpolation can change semantics and argument count, like tcl and bash.)
this isn't true for any half-ass interpretator out there. Perhaps some old PHP executable was slower with different quotes, but it's not true for ruby for decades. In bytecode there's zero distinction between "" and ''. Don't believe me? benchmark.
Exceptions are great. There's an argument to be made that one should handle errors where they occur, but that's often not desirable or even possible.
If I call into a library and something goes awry I don't want to have to care about the inner workings of that library. Most of the time it's sufficient for me to try/catch that call and if it fails I'll handle it as gracefully as possible at my level.
The workflow you're describing is also how it works in languages without resumable exceptions, except that you're forced to acknowledge when a function call is capable of producing an error. Whether you want to ignore the error, handle the error, or propagate the error, it's all the same; you're just required to be explicit about which approach you're taking (as opposed to exceptions, where the the implicit default is to propagate).
Indeed. While it is painful for the people who know they have a simpler architecture, making errors and other cross-cutting effects explicit is necessary at some point. It's essential complexity that shouldn't be hidden; it should be addressed from the get-go. Although the industry largely has the wrong incentives and discourages robust, comprehensible programs.
My belief is that errors should be handled using effect systems. So in the signature but not muddying the actual return types.
Useful effect systems allow the end user to decide where and when to have the compiler enforce errors are handled. Nim has had an effect system for a while but became much more useful when `forbids: [IOError]` was added. It makes it easy to ensure certain type of errors are handled at specific points.
More languages should embrace effect systems. Ocaml's is even used to implement multithreading support, albeit effect systems vary widely in design and theory.
I mean this is how a lot of exceptions are handled, even in C++. You can use noexcept and whatnot and you don't have to change types and propagate them out. In Rust, you do. Java has maybe the strongest system because not only do you have to declare what can throw but it checks it at compile-time. That's, to me, a full featured effect system.
But errors-as-values are all the rage today. But modifying types, especially every type in the chain, is annoying and overly manual IMO.
Yeah, Java had some good ideas but just not quite there on the UX, like many things with Java sigh.
Checked exceptions were annoying because you had to manually annotate the exceptions all the way up your chain. The list of effects should be generated by the compiler, and the IDE should show them when desired. Maybe manually annotated at external API boundaries which double as forcing the API dev to handle unlisted exceptions.
> except that you're forced to acknowledge when a function call is capable of producing an error
Acknowledging the error is handling the error even if partially. The point of exceptions is to only acknowledge error that one can/knows how to properly handle
> The point of exceptions is to only acknowledge error that one can/knows how to properly handle
In Rust this takes a single character. There's effectively no cost to having the programmer acknowledge the error, and there's a large benefit in that you now know that there's no such thing as an error that the programmer ought to have handled but was simply unaware of. That's a huge benefit for writing resilient software.
It's one character in the best case. In the worse case you need to convert the error types to your error type which just re-wraps or replicates the upstream error type. Then repeat this for every library type you use. Zigs error design seems saner in this aspect at least. Rust, IMHO, just makes errors require lots of unnecessary manual labor instead of being smart about it.
Alternatively everything just gets put into a `dyn trait` and you're effectively just bubbling up errors just like with exceptions, but with way more programmer overhead. The performance overhead of constantly doing if/else branches for errors adds up as well in some situations.
Of course a fair bit of Rust code just uses `unwrap` to deal with inconvenient errors.
One thing I’ve wondered about is, isn’t the cost of checking for the failure case in the good case all the time actually worse (even if only slightly) than the cost of not throwing, which is nothing?
There's indeed a predictably present cost for checking for failure all the time. Exceptions, depending on the implementation, often do come with runtime overhead too. If the determining factor is a slight performance gain of exceptions over ubiquitous checking, that would be an exceptional (ha) case. I daresay there are almost always other more salient factors, if harder to rearchitect around.
Boilerplate is code you have to write almost as a pro forma thing. If (in go lang, to continue my example) you're just going to keep copy pasting the same if statement to return `err` up to some higher caller, then why write all those lines when at the top level a single try/catch can remove potentially dozens of lines of code?
Exceptions in C++ are the closest we have to the implementation of the COME FROM proposed in "A Linguistic Contribution to GOTO-less programming".
It takes statically typed C++ and turns it into dynamically typed language.
Throwspec is dead, anything can throw, except when noexcept, then nothing can throw.
It is next to impossible to reason about control flow. Dynamic linking opens whole new dimension of this wormcan. Do you handle exceptions in your constructors? How about constructors of your function arguments?
Not to mention the non-trivial cost in code size, that makes exceptions a non-option for embedded use.
Noexcept is the biggest scam. It basically wraps every call to a noexcept function in a try/catch, and calls terminate in the catch. Actively harmful for performance
[[noexcept]] was never intended to be a standalone performance boost for arbitrary functions – it was proposed & accepted so container types could make the "strong guarantee" (of the so-called Abrahams guarantees): operations can fail, but failed operations have no side-effects.
It allows, for example, std::vector's resize operation to move its contents rather than copying them, iff its element's move constructor is [[noexcept]]. If the move constructor could throw, the items must be copied so the original buffer is unchanged until the entire copy transaction is complete.
My understanding of exceptions implementation in C++ is that there's zero performance cost if an exception isn't thrown. Try... catch isn't implemented as a branch, I believe.
A proper OO support makes difference for some use-cases. That Serenity OS guy is building a web browser and recently spoke about it. Game developers also complain about the lack if it in Rust.
As is typical in Rust-land, lots of talk but implmentations (let alone remotely complete ones) are harder to come by. Partly a procedural and social issue, not just technical queries, which is disappointing.
At least delegation (IIUC closer to concatenation as defined in [1]) is currently being implemented, but I can't say how much weight it can actually carry for the OOP efforts.
I had the same reaction when reading that part, but it's worth noting that the article used this quote in the context of migration from C++ codebases to Rust, not as a critique of Rust in a vacuum. The next part of the quote clarifies this:
> These discrepancies are responsible for an impedance mismatch when interfacing the two languages. Most code generators for inter-language bindings aren’t able to represent features of one language in terms of the features of another.
Function overloads are evil evil evil. Requiring some mental gymnastics by the reader to pretend to be the compiler what function is actually called. That sucks.
Rust does support a form of overloading via custom traits. It’s true that you can’t overload e.g. different numbers of arguments (and the lack of default/keyword arguments is especially annoying here), but you can overload a function by having it be generic over a trait argument and then implementing that trait for each overloaded type.
I would argue that although this is mechanically equivalent it encourages a much healthier design approach.
Take Pattern. One way to look at Pattern is to say that this way we can provide the ad hoc polymorphism of overloading, for functions like str::contains or str::split or str::trim_end_matches -- but as a trait we can see that actually Pattern has discernible semantic properties, clearly a compiled regular expression could be a Pattern for example (and with some feature flags that's exactly correct)
In contrast in C++ there are often functions which use/ abuse overloading to deliver separate features in the same interface, expecting that you'll carefully read the documentation and use the correct feature by passing the right type and number of parameters. Constructors are the worst for this, Rust's Vec::with_capacity gets you a growable array with a certain capacity already allocated ready for use -- C++ does not have such a thing - you must make a std::vector and then separately reserve enough space, but it looks like it might have this feature in its constructor as an overload because the constructor has an overload which is the right shape - however that's actually a very different feature, it will fill the std::vector with default initialized objects, rather than reserving capacity for such objects.
Calling x.some_func() in rust means there is either a type specific function or an impl trait. If more than one option is there rust requires more explicitly calling the types function with x as a parameter.
Reminds me of the trivia given by the instructor when we had a training on "C++ STL" -- guess which method will be called! (Yes, bringing in classes and virtual functions makes it extra fun)
Having learned Go and Rust in the last two years, it occurs to me that -- if this can be made into a trivia, and considering all the things in C++ like friend class, nested class, public/protected/private inheritance matrix and all the "possibilities" out there, it seems this language is seriously f*cked up.
My first job in the late 2000's was at a small university with a home-grown ERP system originally written in the 80s (Informix-4GL). Student records, employee records, financials, asset tracking - everything. It used natural compound keys.
Even worse than the verbose, repetitive, and error-prone conditions/joins was the few times when something big in the schema changed, requiring a new column be added to the compound key. We'd have to trawl through the codebase and add the new column to every query condition/join that used the compound key. It sucked.