This is arguably true for small scripts, but for anything non-trivial I find that static typing means I can be more productive, because type errors are caught straight away (rather than potentially in production), and I can be much more confident in refactoring code because I know certain kinds of errors will be caught by during type checking. This doesn't remove the need for automated testing, but does reduce the number of test cases you have to write.
Things have improved a lot in the Python ecosystem of late with the introduction of type annotations and mypy, so to the extent possible I treat Python as if it's a statically typed language, putting type annotations everywhere and always making sure the codebase passes type checking before pushing a commit.
However, the fact that this is optional sadly means that not everyone does it, and if you're working with parts of a codebase written without this level of discipline, you're basically back to square one. I've come to the conclusion that for medium to large projects, dynamic typing encourages sloppy programming, because it doesn't force people to think as carefully about what exactly their data types are, and whether they're being used correctly.
I agree completely. I feel like there’s a pretty common arc among programmers:
1. learn to program using verbose statically typed languages
2. discover fun dynamically typed languages, eschew statically typed languages
3. discover dynamically typed languages are a shitshow for large real-world projects
4. re-discover fun/less verbose statically typed languages that make working on large projects tolerable
(Obviously, I am talking about CS students here, not about the general public who just want to learn what writing a program means)
- the compiler had better life time inference, so that you wouldn't need to annotate simple functions and introduce beginners to complex issues related to lifetimes, i.e it should "seem" to work like python, initially.
- better learning resources for absolute beginners are developed, step by step tutorial, an easy to install full IDE with friendly error messages etc.
auto x { get_something_complicated() };
or foo(int x) -> auto {
return get_something_complicated(bar(x));
}
so it's less "not-fun" these days. foo(x) -> get_something_complicated(bar(x))
It is not good for readability in large source bases. [](auto&& x) { return get_something_complicated(bar(x)); }
and you can decide whether you want a value (no &'s), an lvalue reference (&), or a forwarding reference (&&). I think with C++20 you can also use a concept instead of auto for a constrained parameter type.It is still more fun and efficient to program in a language that is garbage collected and statically typed.
I'll add to that: static typing also guarantees that your annotations are correct and complete. Prior to Mypy, in the Python world, we would document types in docstrings, which meant that docstrings gradually came to be incorrect--a function that returns a string might sometimes return `None`, but you called it assuming it always returned a string. In other cases, people (including the Python maintainers via the stdlib) will add annotations like "argument 'foo' is a file-like object", which is ... insufficient (do I just need a "read()" method? or do I need to implement "seek()" and "close()" as well?). Ultimately, in the absence of static analysis, a lot of time is wasted reading source code trying to understand the actual type signatures.
> Things have improved a lot in the Python ecosystem of late with the introduction of type annotations and mypy, so to the extent possible I treat Python as if it's a statically typed language, putting type annotations everywhere and always making sure the codebase passes type checking before pushing a commit.
This hasn't been true in my experience. Last I checked, Mypy still couldn't express things like recursive types (think "JSON" or "recursive linked list"), and a lot of things still required jumping through obscure hoops (defining a callback with kwargs). I also couldn't figure out how to export type annotations in my packages, and I had a lot of trouble getting mypy to pull in type stubs for packages that didn't provide their own annotations. In general, Python's typing story still feels very shoe-horned, although it's been a minute since I last fumbled with it, so maybe some of this has improved.
Fully agreed. It's not quite the same as Rust vs. Python, but I've pulled off several sweeping refactors and rewrites in Swift codebases that I wouldn't dream of even attempting with Objective-C.
Story -- I was a very early adopter of Python, back in the mid-90s. When other people wrote their CGI scripts in Perl, I always reached for Python. The first paid gig I ever had was a CGI script ("resume builder") I wrote in Python in 1996. But almost nobody was using it back then, and I'd get quizzical stares from people in job interviews etc. when it came up.
So for many years back then I really really wanted to get a job working in Python. And I was super excited to get a job around the 2001 time frame in it on a pretty cool embedded Linux project.
But my first experience working in a large codebase was disappointing. I could see right away that once many developers started working in that codebase together, best practices were really hard to enforce, and things started to fall apart into a bit of a mess, and lots of problems just showed up at runtime in bad explosions. That was the beginning of my disenchantment with dynamically typed, late-bound languages. They only give you the illusion of fast prototyping. You just shift your time to fixing type and binding errors later in your dev cycle.
Python really improved a lot with the 2->3 transition, and the community has much better best practices now. But I think the core problem with late bound languages remains.
All that said, I'm currently in another window working for my current employer wiring an embedded CPython interpreter into a Rust-based runtime...
Also I think people misread 'python speed', few c++ masters said it was more about prototyping to converge on good overall design rather than suffering the efforts of early iterations in c++ (90s c++).
I still use it for quick one-off scripts to "do things" with bits of data and so on. And as a calculator.
That is the perfect way to describe it! And sometimes you’re out of the dev cycle and into production if your test suite didn’t hit all cases.
I'd add one more addition to use Rust (speaking from an ex-Go dev): Use Rust if you want a robust std lib. Go is good, i used it for ~5 years, but man Rust was a breath of fresh air with the amount of tooling that helped me solve problems. Iterators, are a great example.
I also preferred Crates vs Github Repos, but i think that's just personal preference.. not objective.
Here's an example where the iterator-chain version is way more complex:
let posts = read_dir(source_directory)?
.filter_map(|result| {
(|| {
let entry = result?;
let os_file_name = entry.file_name();
let file_name = os_file_name.to_string_lossy();
if Self::is_bundle(&entry)? {
self.parse_post_bundle(&file_name, &entry.path())
.map(Some)
} else if file_name.ends_with(MARKDOWN_EXTENSION) {
self.parse_post(
file_name.trim_end_matches(MARKDOWN_EXTENSION),
&mut File::open(&entry.path())?,
)
.map(Some)
} else {
Ok(None)
}
})()
.transpose()
})
.collect::<Result<Vec<_>>>()?;
Here's the imperative version: let mut posts = Vec::new();
for result in read_dir(source_directory)? {
let entry = result?;
let os_file_name = entry.file_name();
let file_name = os_file_name.to_string_lossy();
if Self::is_bundle(&entry)? {
posts.push(self.parse_post_bundle(&file_name, &entry.path())?);
} else if file_name.ends_with(MARKDOWN_EXTENSION) {
posts.push(self.parse_post(
file_name.trim_end_matches(MARKDOWN_EXTENSION),
&mut File::open(&entry.path())?,
)?);
}
}If you're going to write an iterator chain in an imperative way, then you might as well right it imperatively.
A few notes:
- you can compare `&OsStr` with `&str` without `to_string_lossy` any explicit conversion (`&str -> &OsStr` done in `PartialEq` is basically free)
- you can get extension from `Path` by using `Path::extension`
- you can get filename without extension by using `Path::file_stem` or `Path::file_prefix`
- making your parse functions take the same arguments makes it a lot cleaner
Something like this:
read_dir("")?
.map(|e| e.map(|e| e.path()))
.map(|path| {
path.and_then(|path| {
if Self::is_bundle(&path) {
self.parse_post_bundle(&path.file_name().to_string_lossy(), &path).map(Some)
} else if Self::is_markdown(&path) {
self.parse_post(&path.file_stem().to_string_lossy(), &path).map(Some)
} else {
Ok(None)
}
})
})
.filter_map(Result::transpose)
.collect::<Result<Vec<()>, std::io::Error>>()
// Ignore this, just an example
fn is_bundle(path: &Path) -> bool {
path.extension().map(|ext| ext == "bundle").unwrap_or(false)
}
fn is_markdown(path: &Path) -> bool {
path.extension().map(|ext| ext == "md").unwrap_or(false)
} path.extension().map(|ext| ext == "bundle").unwrap_or(false)
vs (path.extension() == "bundle").unwrap_or(false)
or further up .map(|e| e.map(|e| e.path()))
vs
.map(|e| e.path())
Is the .map keeping any value wrapped in an iterator so you can map again? I thought it was interesting what you wrote looks a lot like what I'd write in powershell.extension() returns an Option<&OsStr>, because not every file has an extension https://doc.rust-lang.org/stable/std/path/struct.Path.html#m...
map on option says "run this closure on the value if it's Some, or do nothing if it's None https://doc.rust-lang.org/stable/std/option/enum.Option.html...
So after the map, we now have an Option<bool>. The unwrap_or method will say "give me the value if it's Some, and give me the argument if it's not https://doc.rust-lang.org/stable/std/option/enum.Option.html...
That means we finally end up with a bool.
(path.extension() == "bundle").unwrap_or(false)
This code wouldn't work because you're comparing an Option<&OsStr> to a &str. Even if that worked, you'd have a boolean, and unwrap_or doesn't make sense on booleans, you'd just leave off that part.Why not `((path.extension() == Some("bundle"))`? I don't know, it didn't work, and I didn't want to spend too much time on it.
`read_dir` gives us an iterator over `Result<DirEntry>`, so `e` in that map is a `Result` and not a `DirEntry`. It's two different "map" functions - on Option<T> and Result<T,E> it maps "T", but on Iterator it maps "Item" from that iterator.
Here is an explanation why `read_dir` give you a Result - https://doc.rust-lang.org/std/fs/fn.read_dir.html#errors
I may just have more time to dick around, but I've been forcing myself to write everything as an iterator in Rust (instead of a for loop) just to learn the paradigm and, honestly, it's been pretty great.
First, there is a performance impact. You may think this would be small, but, in my experience, it can be relatively large. There must be several reasons of which I am ignorant, but they probably include mutable no alias inside closures, bounds check elision, etc.
Second, there is a mutability advantage to just using filter/flatten/map/collect for 90% of your use cases.
Third, the more advanced iterator methods will cover the other 5-10% of your use cases, and will make your code much easier to read, once you understand how to use them (if you're trying to learning something/anything more complex at the same time, save your brain power!). I don't have a single traditional for loop in my personal projects.
My take on iterators is, for iterator/Rust beginners (like me!/perhaps not you) -- the best way to learn is to get it working as imperative code, and when you have the time, go back a rewrite as an iterator. In the beginning, it's super frustrating. But you'll get better and better and, after awhile, you'll much, much prefer writing as an iterator. You won't even think of using a for loop.
for item in iterator {
vector.push_back(foo(item));
}
You're not explicitly indexing the source iterator or the destination vector at any point."I think people make too big a deal about iterators. The simple for loops that they’re fit to replace aren’t that hard to read or write, so they aren’t causing real problem"
> I disagree. If I can avoid an index variable, I’m going to have less bugs since I won’t have to index
Which (given thread context) sounds pretty clearly like he's advocating against `for item in iterator {...}` and in favor of iterator chains. If there's another way of reading that comment, I'm not seeing it. :)
Not only that. In safe Rust vector indexes are always bound-checked that slows execution a little bit. Iterators over a vector, on the other hand, do not suffer from bound-check overhead.
However one reason to prefer an iterator chain in some cases is that the chain might imply an optimisation you'd otherwise have to write manually and might not think of.
For example if I have N things, and I map them, perhaps more than once, but I'm definitely getting N of whatever the output of the last map was, the iterator chain might very well see that I don't actually have any way to change N and so when I collect() that into a Vec the Vec it gives me is constructed with capacity N, saving the (amortized but non-zero) cost of growing it during insertions.
Imperatively I can remember to write Vec::with_capacity(N), and that's safe of course but it's an extra step to remember.
The imperative approach does particularly shine on the opposite edge of this, if I know that I'm getting no more than 100 items out of this pipeline, despite putting N in, Rust almost certainly won't see that and won't make Vec::with_capacity(100) for a collect() call whereas my imperative code can just explicitly write Vec::with_capacity(100) or whatever.
If you use "take" for example, collect should do the right thing: https://doc.rust-lang.org/src/core/iter/adapters/take.rs.htm...
It will only care about your hints if you implement the unsafe trait TrustedLen (which promises those hints are correct)
Take is indeed TrustedLen if the Iterator it's taking from is TrustedLen (as in this case it can be sure of the size hint)
If you get to ~100 via some means other than take()ing 100 of them, chances are you don't have TrustedLen as a result.
https://doc.rust-lang.org/src/alloc/vec/spec_from_iter_neste...
I wonder how the hell I didn't see that when I was tracking down how collect() ends up caring about TrustedLen.
Thanks.
I personally use iterator-style for 1-3 simple ops, but go into a loop for anything more complicated. The functional part of me wants to force iterator style but I fight it for complex use cases because it is irrational on my part, and I find loops easier to read for stuff like that.
I however will often have a dozen maps/filters/etc and that style in imperative is what, nesting repeatedly or using `continue`. Meh.
If your Iterator consists of a single `filter_map` i can see why you don't get much value out of them in that case. It's because.. well, there's not much value to get out of it.
The tooling is the #1 reason I'd like to learn rust. I have not kept up with C++ and I'm not sure I ever will... sometimes plain C seems more straightforward. But those languages (C++ especially) have suffered from lack of standard tooling, in my opinion.
Even C# being a few years younger than Java seems to have made a huge difference in tooling availability (I'm sure having a single corporate driver during its lifetime made a difference, too).
I'm excited by the prospect of a compiled language with modern tooling.
Rust has been very rewarding in that it largely fixes all of C++’s tooling and language problems. It very much is a significantly improved C++.
Overall tooling and std lib are better on Go, actually there are not many languages that are on part with Go to that regard.
When you see what you can do with the single go command, it's pretty powerful.
Go doesn't have a standard library problem, it has a userland language problem.
* No sum types / algebraic data types in 2022.
* No exhaustive pattern matching in 2022
* No move semantics / Uses GC
* No borrow checker
* Still suffers from nil problem / No type-safe nil / No type-safe Optionals in 2022 -- (I can show you all the nil panics in kubernetes logs if you like)
Current go users are already sold so they don't expect more, and that's fine, but further evangelism will require a better host language.
Notably, despite its shortcomings, Go is still perhaps the most productive programming language for general purpose applications. It turns out that type system features are neat, but tooling, ecosystem, simplicity, etc outrank type system features by quite a lot. Rust does very well here too, but it’s choice of borrow checker over GC amounts to trading off a lot of productivity in the general case (but it makes sense for Rust’s goals!).
[0]: https://tokio.rs/
I'd argue it's almost certainly something that should NOT be in the standard library. If anything, the feature was added to Rust too quickly and has way too many rough edges.
I've noticed however that there has been an uptick in great libraries over the last 2 years, with examples like pola.rs, rust-bert, tokenizers etc. starting to build momentum in the ecosystem.
As I understand it, the article talks about switching from python to rust in a non-trivial database service that is required to be fast and robust. I can't imagine python to do well in this regard, especially when C/C++ extensions are required to enhance the performance.
I also agree that 'Python has been a strict liability and the promise that you can "just rewrite the slow parts in C/C++/Numpy/etc" has never held'.
I just don't agree those points necessarily imply "avoiding Python for new projects". Even in the case of the original article, was it, in hindsight, a bad decision to start with python? I'd argue not necessarily. Python is still great for quickly hacking together something that works, not necessarily fast or maintainable, but if you're under time pressure or you're still trying to feel out the market fit for your product, it's not a great idea to waste brain cycles to deal with borrow checks for example.
Python's weaknesses only show when you need to scale, both on the performance and on the lines of code / number of collaborators metric, which is a problem you have only when the project succeeds. Avoiding python because of these problems reeks of premature optimization, unless you know it's how the project will end up in advance.
As other sibling comments point out, python is still great for small scripts and weekend projects. Perhaps you do mean 'avoiding Python for new projects that are expected to grow non-trivially', which is fine advice. I'm just a bit tired of all those over-reaching blanket statements people make regarding software design that only really makes sense in a limited context (here's a funny one I've heard recently: never write nested-for-loops because it's sloooww). It would be unfortunate to have the message "don't use python for large projects" be misinterpreted as "python sucks no matter what".
The article specifically talked about following the conventional Python advice to use C/C++ for the fast parts and Python for the "glue". This is precisely where Python people say Python shines.
> I just don't agree those points necessarily imply "avoiding Python for new projects".
You should avoid Python for new projects because you never know at the outset whether you will run into a performance bottleneck that can't be resolved by "just rewrite in C/numpy/etc" and there are other languages that are better than Python at both its strengths and weaknesses. The odds of painting yourself into a corner with Python are very, very high, and there are other languages that are better than Python at just about everything (the only area where Python still dominates is data science exploration libraries). Even if you don't paint yourself into a corner, you'll be dealing with lots of surprising papercuts: if you want reproducible builds, you'll have to wade through an ecosystem of package managers each with their own pitfalls including long build times (e.g., I saw pipenv take 30-45 minutes just to `pipenv lock` on a small-medium sized project); developing with docker basically doesn't work on mac (and windows?) because the VM uses all available CPU marshaling filesystem events over the guest/host boundary; your developers accidentally pulled in an API SDK that makes sync I/O calls (or else does some CPU-intensive task) which seizes up the event loop bringing the process down (and the failure cascades to remaining instances bringing the whole app down); and then there's the "regular" dynamic type system errors like forgetting to "await" things or missing/incorrect type documentation.
> Even in the case of the original article, was it, in hindsight, a bad decision to start with python? I'd argue not necessarily. I'd argue not necessarily. Python is still great for quickly hacking together something that works, not necessarily fast or maintainable, but if you're under time pressure or you're still trying to feel out the market fit for your product, it's not a great idea to waste brain cycles to deal with borrow checks for example.
My point isn't that Python is bad for literally every project--I actually think this project was probably a best-case scenario for Python (they didn't describe many of the issues I described in my previous paragraph) and they knew ahead of time where there performance issues would be so they were able to start with an architecture that was amenable to "write the slow parts in C/C++". My point is that there are languages that are better at fast iteration than Python (namely Go or TypeScript) while also excelling at Python's weaknesses (performance, tooling, distribution, type system, package management, etc etc etc). I think it's incredibly reckless to start a new commercial project in Python in almost all cases (with a possible exception for data science, but even then I would look hard at everything else first).
> Python's weaknesses only show when you need to scale, both on the performance and on the lines of code / number of collaborators metric, which is a problem you have only when the project succeeds.
This might be true for certain Django CRUD apps, but it's patently untrue in the general case. I've seen Python work fine for a prototype data science app, but fall over when real customer data was introduced. Similarly, I'm currently an SRE attached to a Python project which falls over regularly because of some event loop issue or another (often someone pulls in an SDK that makes sync calls under the hood, or else some CPU-intensive task blocks the event loop). I see stuff like this regularly, and it never happens in our Go apps.
> As other sibling comments point out, python is still great for small scripts and weekend projects.
Granted. Do what you want in your free time, but it's risky to use Python for new commercial apps.
> Perhaps you do mean 'avoiding Python for new projects that are expected to grow non-trivially', which is fine advice.
Yes, this is what I meant. I didn't mean "hobby projects".
> I'm just a bit tired of all those over-reaching blanket statements people make regarding software design
I don't usually make blanket statements, but Python really is so bad that this is a blanket statement I'm pretty comfortable with. I've scarcely ever heard of someone getting burned for picking Go over Python (there was some Python2 veteran who wrote an angry blog post once about Go back in the early days, but never since). Python just refuses to adequately progress because it wants to maintain compatibility--that's fine, but as professional engineers we should understand that the rest of the ecosystem is moving beyond where Python is willing/able to go, and it's no longer an appropriate tool for the overwhelming majority of new projects.
> I don't usually make blanket statements, but Python really is so bad that this is a blanket statement I'm pretty comfortable with.
Speaking as someone who agrees with you that something static is very likely better than python for a large project, how do you square this with the existence of a lot of Python projects, even "very commercial", that seem to go on just fine?
It's a fine theory and something I instinctively support, but I don't see measurable evidence in support of it.
These guys and girls are heroes. They are like the plumbers and electricians working at 3:00 AM so your water and electricity never stop while you're awake.
"Just fine" is likely only your external observation. I'd give them medals and help them retire at 35 for all the horrors they have endured. It's "just fine" because they tirelessly made sure of it.
Things were just fine despite Python. Not because of Python.
Plus, none of us has statistics that prove a theory beyond any doubt. Forget about that; this scarcely (if ever) existed in the programming world and likely will not exist anytime soon. We should apply our experience and the intuition that grows out of it.
Finally: a pile of anecdotal evidence can be treated as an objective evidence with, say, 50-60% probability. Insisting that the real world must comply with academically pure practices is not going to get us anywhere (and is misguided).
The cognitive dissonance embedded in this statement is rather striking, considering Django itself is written in Python and is known for its ruggedness and code quality. Django manages all of this without even taking advantage of somewhat newer features in Python like typing.
Most startups that YC has funded that became successful (Series B or higher) were written in Python or Ruby. Now you can say that this is a tradeoff for post Series B, and for that I don't know. I've never worked on a massive Python mono-repo for a company that size. But I know what I've done in Python and, yes, it includes performance optimization in Numpy / Scipy / Cython, and other than Ruby, no other language comes close to the developer performance I see with Python and the ecosystem around it.
That said, there is a good and a bad way of writing Python. You absolutely need tests and lots of them since there is no compiler for vanilla Python. Mypy helps a ton too and so do the linters and even Black. Also, get pdb++ and customize it. The tooling helps a lot.
Now I haven't ever written production Rust, but I have for Go and many other languages, and so Rust may be faster, but other than Ruby I haven't seen anything close.
> Most startups that YC has funded that became successful (Series B or higher) were written in Python or Ruby.
It depends on what your goal is. If you want to get rich off of VC money, Python and Ruby might be a good fit. If you, on the other hand, want to write good, performant, maintenable and (relatively) bug-free software, then there are better choices.
You can do that part when the money comes in.
If you don't take investors' money, then it's theoretically feasible, but, in practice I've seen companies stuck with their Python/Ruby stack for 10 years or longer, always wanting to rewrite but never having the time. Microservice architecture helps (not that it's not a can of worms of its own), and these companies tend to write new functionalities in their desired language (such as Go or Scala) and deploy them as separate microservice. The old code remains in Python or Ruby though, and now they have a multilingual mess that is near-to-impossible to maintain for a small team. Why not start with a better language in the first place?
So, what was the percentage of YC startups that started with Python or ruby and what was the share that became successful?
Is there data on startup lifespan ranked by tech stack? Genuinely been looking for this for awhile now.
But it is indeed quite fast when you are just trying to get your first file of code written.
There are more rungs on the ladder.
Maybe it's just that I don't understand what clangd provides that's so great, but rust-analyzer, despite a few hiccups I've had with it, seems completely amazing at what it does.
What clangd does is work.
So -- this guy used an extension (named Rust) with rust-analyzer, a configuration known to not work, it didn't work(!), and the extension author recommends he tries the extension made for rust-analyzer.
Hardly a case for the ages. Guy uses unsupported config and things don't work?
> What clangd does is work.
Don't doubt it. I'm just saying -- I haven't had any problems with the rust-analyzer extension since it became the Rust default. But, yes, I had a few hiccups and crashes beforehand, no doubt. I just have to imagine it's both younger, and doing more/different things than clangd.
If I search for "Rust" in the VS Code Extension Marketplace, it shows the extension you linked to... but it is clearly marked as deprecated, and VS Code won't even let me install it. So, it’s even more of a non-issue today than it was a year ago.
The actual rust-analyzer extension works fine for me. I think Go's tooling is slightly better in my experience, but both are far better than any experience I've had with C++ tooling.
What I take exception with is "it’s always LLVM’s fault, I know". If you're going to trash other people's hard work in such a glib manner, it behooves you to know the details of what you're talking about. Tell us, why would a language server that doesn't generate any code use LLVM?
One of several issues around engaging with the Rust community at least as personified on HN is that it's very difficult to see any other outcome than being backed into a pretty extreme corner: I started my involvement with this thread saying that I like Rust and want to like it more, but found taking a piece of software that seems interesting on its own merits and headlining the Rust part (whether due to the original post authors or the submitters, makes no difference) contributes to a skepticism-inducing sense that there's an agenda other than "cool software" sitting close to "Rust" in the embedding space.
And, if you care to, you can watch me go from "Rust is cool, great stuff written in it" -> "hey some of these claims are seeming extravagant" -> "ok let's not get carried away, the cited examples are mostly Haskell semantics and clang optimization" -> "alright I'm calling bullshit that all the tooling is superior, that's fucking stupid" give or take. It's a pragmatic cool programming language that people write great software in. That's plenty, that's more than most languages ever achieve, and Rust is far from done achieving things.
`rust-analyzer --version` reports: "rust-analyzer 2022-04-11" on my primary Nix shell. `clangd --version` reports "clangd version 13.0.1" on the same env. I drive both of these things on the command line, in CI, and via `emacs` (on `eglot`) every month at a minimum, every week almost certainly, and usually every day.
I have seven nontrivial elisp `defun` and/or `defadvice` (deep into `rustic-mode`) stanzas for keeping `rust-analyzer` working ok. I have to defeat it's naive `Cargo.toml` search in interesting projects, I have to turn off big blocks of my buffer going RED ALERT RED while a brace remains missing which is statistically most of the time, and most importantly, I have to manually restart it with save excursion trickery so that I can survive a crash without blowing my session. Some of these defects may be as old as earlier this year when I last audited all that.
For `clangd` I have zero such nonsense. Which is completely understandable, it's an older and therefore more mature piece of software. That's not a knock on Rust or `rust-analyzer`.
But I made the fatal mistake of posting the wrong link to the issue tracker, for which my reward was to cause the "Flying Fucking HN Rust Brigade Gangbang Comment/Downvote Battalion" to put the Batman light on.
If you folks are comfortable being the "extremists people are too tired to fuck with", then do you. But I know for absolute certain that I am not the only person doing some level of Rust adoption in spite of this bullshit rather than because of it.
Sorry, but this is patently false and is just straight-up misinformation. rust-analyzer is an extraordinary piece of work with careful, well-thought out idiomatic software design, easy usage and relatively low productive defect count.
Rust, the language, itself might be rather over-hyped here on HN - its most loved status will drop significantly with larger real-world adoption. However, the rust-analyzer project isn't overhyped and is a clean and elegant product of software engineering. I wish I was good enough to design and code language software like this.
I worry about the more ardent Rust enthusiasts because it seems popular in those circles to embrace an attitude that we’ve somehow arrived at the right conceptual framework.
Rust is a competent, pragmatic embedding of a few of the big ideas from serious PLT into a well-optimized C++ compiler toolchain, and it’s cool and useful as a result.
But I hope to God it’s not the endgame on fast ML/Haskell/Lisp.
But I'd be even more disappointed if the best language people are writing software in when I retire isn't even Rust.
Naturally one can argue they weren't popular, with exception of Object Pascal.
Maybe you can explain why Gophers are such whiners always crying about every freaking comment at any forum.
Go has had templating built into the standard library since... forever?
And Go templates can use other templates, as long as they're all loaded into the same context, if that's what you were getting at.
simple examples of Go's built-in templates: https://gowebexamples.com/templates/
templates using other templates: https://levelup.gitconnected.com/using-go-templates-for-effe...
Look at this page: https://quarkus.io/guides/qute-reference
and read about “Template inheritance” in 3.5.6 to see what I mean.
Go doesn't really do template inheritance in the way I think you're meaning. Go prefers composition over inheritance, and that carries over to the templating system. Go templates let you pass a parameter to the template you're calling, which I believe addresses the need you linked to. Calling another template is not just simple string concatenation; the other template can act on input provided by the calling template.
There are a lot of different ways to approach templating pages in a web app. I think Go's templating system is one of the least objectionable ones I've used.
I have no experience with Quarkus, so I can't say how good or bad that one is, but my experience with Ruby templating systems is that people often start mixing complex logic into the templates, including database calls, and that causes things to get messy. Go's template environment is very minimal by default, and you can extend it with custom functions if you want to give more power to the templates, but Go encourages you to compute your data ahead of time, then pass it into the template... and just have your template be a template for formatting the data.
Sometimes you can solve social problems with technical interventions, but at this point, someone needs to sit down and talk :-)
> Go encourages you to compute your data ahead of time, then pass it into the template
Same elsewhere including modern PHP and Java.
The thing that is missing in Go is a natural way to group together what goes around the content.
Sure, stringing together the templates (yes, I know the other template can act on input provided by the calling template, but they are still stringed together) feels weird when what one really wants is to fill in slots in a design, which is my thought process.
https://github.com/dotnet/Docker.DotNet#example-create-a-con...
That seems like a bold claim? I've also been writing Python for a decade and it takes no time at all to whip up a small script. I have no doubts the Rust version will be much faster, but stepping into a new language would take me at least longer to write the script than an equivalent version in Python. Plus there's all the other stuff to learn.
Once you get past learning the basic syntax (creating variables, loop structure, assignment/etc.), you still have to pick up new things, like executing your work with parallelism, downloading files, parsing and dumping JSON, connecting to databases... Each new topic introduces a bunch of stuff that takes a little bit to figure out. I guess you could get through most of it in a few hours though if you had the right problem to work on.
But still, there's the muscle memory aspect. Can a new language really sink in so fast that it's faster to use than Python after 10 years of writing Python?
I'm not sure I understand. Was the typo in something like a dict key? Or did you add two of the wrong variables together? How did another language protect you from this? I fail to see how you couldn't also add two of the wrong variables (with the correct type) together in a different language.
For full projects outside websites, Rust for almost every case.
The important factor here is "with all strict checks in place". Whenever I hear fellow FE devs complain about how they dislike Typescript, the complaints often seem to center around how it doesn't really offer that many guarantees... only to find out that their typechecker is set to the least strict settings!
I'm not gonna lie, though: the transition from JS to TS absolutely comes with growing pains. This obviously includes actually updating the codebase and adding types, as well as the developer onboarding/learning process. Personally speaking, I found it very jarring to go from a permissive, lightning-fast JS dev cycle to a more methodical Typescript one.
And don't get me started on how frustrating it can be to diagnose weird type errors related to generic types :D
I often work on codebase that involves typed async Python plus a collection of Rust CLI tools that do most of the heavy lifting. And it's really not bad at all.
1. Tooling. `go build` just needs a `go.mod` file with a list of dependencies. There's no MSBUILD stuff (I think dotnet had a similar JSON file format, but then went back to MSBUILD?)
2. Runtime. I love that Go's default is static, native binaries, and that the ecosystem revolves around that. I especially love that this enables ~2mb Docker images. (I'm of the impression that you can do this in dotnet, but it's not clear to me how easy it is in practice or if there are performance tradeoffs)
3. Value types. Go doesn't have a classes; everything is a struct, and polymorphism and so on work on structs.
4. No inheritance. I'm pretty militantly opposed to inheritance. I know you don't have to use it in C#, but in Go I also don't have to deal with APIs or coworkers that assume inheritance.
That said, these aren't major things; I'm sure C# is a great language in general, and I'd definitely reach for if I wanted to make a Windows GUI app.
C# and .NET's main strength, IMO, is the web API framework and general purpose utility; everything from web apps to Unity and Godot. It's easy to start with `dotnet new webapi -minimal` will give you a scaffolded project which is pretty similar a barebones Express or Flask app. But you get much more headway and scalability and the room to layer more complexity as needed.
It's main detriment is, as you mentioned, the need for a somewhat heavy runtime and the relatively slow cold starts which make it a poor choice for things like CLI tools and serverless functions.
That said, Go is a fine language. It doesn't have exceptions last I checked, so that could be a deciding factor. Exceptions are handy in a lot of cases.
Their performance is, impressive, to say the least.
Once you understand how the borrow checker works and how to write/structure your code in a way that the borrow checker is happy with it sort of becomes second nature.
I think by side effect coding in this style does lend itself to simply writing better to understand code. Nowadays I just write my code this way without much thought. It happens effortlessly. I find myself writing programs in other languages like I would in Rust nowadays.
This is compared to when I started and ran into borrow checking issues many times a day. Additionally the borrow checker has improved since then and NLL borrow checker will accept more programs as valid than the previous one.
The point is that you pay more upfront but can then iterate much more confidently and quickly.
Go is also simpler because of the GC, given that Rust can get a bit tricky with lifetimes. Go routines and channels are a delight to use together with contexts.
Rust allows for a lot more ways to solve one problem, i.e. you can iterate over an array, of use a loop. Go only provided a for loop and that is pretty much the only way to iterate over something.
Some people might find this limiting but I find it refreshing given that I can focus on what I need to do instead of how I should do it.
I wouldn't recommend Typescript for CPU-bound tasks, but for I/O-bound tasks with complicated business logic it's type system is on the order of magnitude better than Go, and makes you able to get really close to the "make invalid states of the system a type error" ideal of functional languages like Haskell, while still being a lot more approachable.
I've had experience with large codebases in Python, Java, Go and Javascript before.
Python - as everyone already said, just sucks. Refactoring is a nightmare, you have to test every single line of code for any sort of dependability and that makes refactoring even harder. It allows for too much meta-programming and within 3-6 months your codebase is legacy.
Java is just not fun to write for me, but Kotlin is kinda fun and I could see myself choosing that. The problem is that my product deals with a lot of deeply nested, user-defined objects, and converting JSON (or YAML, or XML) to POJOs is a verbosity nightmare.
I just fucking really hate Go. I can't explain it, but I find the language infuriating. It somehow manages to be less expressive, more verbose and more straight up boring than all the other options.
As a Go fanboy, this made me chuckle. :)
> I can't explain it, but I find the language infuriating. It somehow manages to be less expressive, more verbose and more straight up boring than all the other options.
I sort of get it. I think people fixate too much on the for loops and the `if err != nil` boilerplate, but there's definitely some validity with respect to "how to properly annotate errors" and emulating enums (in the Rust sense of the word) is pretty error prone and it still doesn't get you exhaustive pattern matching.
The stuff I like about Go:
* Single static, native binaries - being able to just send someone an executable is pretty nice, not needing to make sure people have the right libs and runtime installed is phenomenal.
* Great tooling. I love that the Go tool takes a list of dependencies. Unlike Gradle/CMake/etc I don't have to script my own build tool in a crumby/slow imperative DSL.
* Compilation speed. Go compiles really fast.
* Small language, easy learning curve. Any programmer can read just about any Go project with very little experience. Any Go programmer can jump into any other Go project and be productive immediately (no extensive configuration of IDE or build tools or anything like that). You can also write surprisingly abstract code without reaching for generics, but you will likely have to change your programming habits.
* Value types done right. Most other mainstream languages don't have them, and the ones that do very often implement them poorly (e.g., IIRC in C# you can define a type as a struct or a class and only structs can be value types, and classes are more idiomatic).
* Go eschews inheritance and prefers data-oriented programming over object-oriented programming (although OOP is poorly defined and some people think it means "anything with dot-method syntax").
* I'm just more productive in Go than any other language
Stuff I don't like about Go:
* No enums
* I wish there were fewer tradeoffs between abstraction and performance
* Needs a better system for annotating errors
* Doesn't run on embedded systems (although I've heard good things about TinyGo)
* Doesn't make me feel as clever (this is a nice property for professional software development, but for hobbies not so much)
On balance, I think Go is the better language, but I can also understand why it chafes people. Different strokes. :)
My favorite language is by far Java with Go a close second. I dislike the others. The only thing I wish Go had is more functional support, but only in the style of Java's fluent API. I strongly dislike PHP, JS, and Python functional style and would almost prefer they not exist.
Don't need those functional constructs at all, allthough I'm using them quite often in Typescript.
But for Go - please no. Go is like C. Adding things just makes it worse.
This is how code is supposed to be written. Sort out the logic first, don't write the actual implementation details until later (models, http handlers, file parsers).
The support for this type of SOLID design is much more common in Go than in Typescript projects which often seem to inherit bad practices from Javascript developers.
I don't hate it like you, I just find it amazingly backwards, and I think I can explain why:
The community has the same persistence as certain teachers when it comes to locking things down and making things harder than necessary for no good reason, all for our own good.
Prime example: announcing a supposedly cutting edge language without generics in 2009, launching version 1.0 without it in 2012, 8 years after notoriously conservative Java got it, then waiting until 2022 before listening, that is some hardcore teacher mentality IMO.
I haven't used Go for years but it was sorely lacking last time.
Are you sure you aren't suffering from the Stockholm Syndrome?
I’ve been looking at Nim or Scala for the backend. It’s either that, or just dive into Rust.
It really seems if you don’t want to use Node, Java, or Go the available choices for a statically typed backend get quite slim.
Through this lens, the semantic equivalent of C++ templates in Rust isn't generics, but macro_rules! - but for most uses of C++ templates you would rather use generics and traits.
Even macro_rules! aren't as capable as modern C++ templates with types. I am let down with how lacking Rust is when doing compile time type based programming. There's no CTTI or RTTI in Rust. The procedural macros don't have access to type information, just syntax. The constexpr equivalents are so limited, etc.
What I really like about Rust generics on the other hand is that, while limited in many ways, those limitations allow them to be complete in the sense that as far as types concerned, the generic signature is the law of the land; it'll compile with any type fulfilling the trait bounds, and the implementation behind the signature is fully limited to exactly what's provided by the trait bounds.
Specifically, I posit that returns on type system features diminish--there's a ton of value in going from a dynamic type system to Go's pre-generics type system, but every addition thereafter offers decreasing value.
It is, but does it really matter when you write backend? I've written and operated Node-based backends at scale, and I can't honestly remember any bugs or outages that would have been prevented by this.
At the other hand, there's a lot of potential bugs and problems that didn't happen because proper usage of TS's type system prevented me from committing them.
Again, it's not the end-all-be-all, but it's a surprisingly nice property.
I'm interested in producing fullstack typesafe web projects using typescript on both backend and frontend.
My question is how do you scale nodejs since it is single-threaded? Say I need 10k concurrent connections. These connections doesn't have to be all hammering the system at once but I need to accept 10k connections and perform something like 2k requests/s.
How do I approach this? One server with clustered nodejs? pm2?
I ask because there are a ton of outdated info on how to scale nodejs.
Some rudimentary benchmarking with clustered or pm2 nodejs tells me that 95 response times go from 30ms to 1000ms once I hammer the server. Whereas Go and C# keep it under 100ms without any effort or extra tooling, just using standard library.
But I write software not to enjoy the process, but to solve business needs. And using Rescript, Scheme or Haskell drastically limits the talent pool and rises the cost. Which, BTW, can be very good for some businesses, that would want to hire only the best programmers anyway and are ready to pay for it. But that's some, not all.
I just wish ReScript would have gained traction and replaced Typescript. But who knows maybe it can still happen.
TypeScripts's type-system soundness holes kill the fun with that language very quickly, imho. That was a really big disappointment for me.
A "static" language that compiles fine and than crashes at runtime with complete WTF-bugs is not much better than a dynamic language. You just can't trust the compiler and need to double check every line of code anyway. So there is not much additional value in this kind of "type safety".
I would prefer Rescript of Scala.js anytime to TypeScript. Got really disillusioned by TS.
If a package does not supply its own types (increasingly rare now), you can generate stubs and put them in typings/$package.
It's picked up a bunch of bugs for me already. And if runtime performance is not a priority, I'd rather write Python than Go or Rust any day. It's more fun and more expressive.
https://numpy.org/devdocs/reference/typing.html
Which is cool, even strongly typed languages don't usually support that.
Why not D?
Nothing you said is verifiable.
Right, I was pretty explicitly speaking from experience. See my first paragraph.
> If Go replaces your Python this easily, you have always been writing Go
Go is only a 10 year old language, and I've been writing Python professionally for 15 years. I definitely have more hours on Python by a wide margin.
> The abstractions available in Python are in a totally different class than that of Go.
Python definitely is more abstract than Go, but I don't think that's a feather in its hat. Moreover, most of my criticisms of Python had nothing to do with its abstract nature.
I don't doubt this in the least. I've been a professional Python developer for 15 years, and I can't believe Python ever had the reputation for "high dev velocity" beyond toy examples. In every real world code base I've worked in, Python has been a strict liability and the promise that you can "just rewrite the slow parts in C/C++/Numpy/etc" has never held (you very often spend at least as much time marshaling the data to the target language format than you gain by processing in the faster language and you have all of the maintainability problems of C/C++/Numpy). Python trades developer velocity for poor runtime performance.
I don't think Rust is the paragon of high dev velocity languages either, but it seems to be more predictable. You don't have something that works as a prototype, but then you run into a bunch of performance problems (which virtually cannot be worked around) when you go to productionize, nor do you get all of the emergent quality and maintainability issues that come about from a dynamic type system (and last I checked, Python's optional static type system was still pretty alpha-grade).
I strongly recommend avoiding Python for new projects. Use Go if you're looking for an easy GC language with max productivity and a decent performance ceiling. Use Rust if you're writing really high performance or correctness-is-paramount software. I'm sure there are other decent options as well (I've heard generally good things about TypeScript, but I'd be concerned about its performance even if it is better than Python).