Hacker News new | past | comments | ask | show | jobs | submit login
Our journey to type checking 4M lines of Python (dropbox.com)
207 points by signa11 on Sept 7, 2019 | hide | past | favorite | 107 comments



The worst, buggiest, least maintainable code I've ever dealt with was a 500ish line (heavily tested!) untyped Python file at my last job. Caused more trouble than all the Java and C++ and Go combined.

My personal experience with compile time type checking is that the benefits kick in for a single developer project within a week, and for a multi-developer project immediately.


I am not going to dispute the value of compile-time type checking. And I admit to only having had time to skim the link at this point but it looks like a great read so I bookmarked it for later. That said...

Python can be as strongly typed as you like it to be. But of course, with Python, instead of insisting on an incoming parameter being of a particular type, the thing to do is Pythonically ask nicely if it can be the type that you want. If you need parameter x to be a float, call float(x), which will raise TypeError or give you a float. And of course, it gives the implementor of the class the option to implement __float__().

With a small amount of effort, you can design all of your __init__() constructors to do that style of validation. Yes, it requires some elbow grease. But you will not be able to pass bad stuff to my __init__ constructors if I don't want you to. And if you want some parameter y to be an instance of Foo, just call Foo(y) and if it is already a Foo, you get the Foo, and if it can be made a Foo, you get a Foo, and if it can't, you get TypeError.

So of course you actually need to exercise the code also, which is where the hypothesis package comes in.

Anyway, I think one of the reasons Python is popular is that it is very consistent about types. Haskel and Rust are also very consistent about types -- most other languages, not so much. Give me one end of the spectrum or the other. The murky middle is suboptimal.


This doesn't work as well as you'd hope.

It requires among other things, a unique and toilsome coding style, constructor overloading (which isn't a first class citizen of python), and you only get late runtime validation (which means, for example, you can easily miss type validation on only one side of a conditional).

Something like pytype detects such errors without any code modifications.


You can get compile time validation for this type of construct using typing.Union.

But once you have type validation i don't really see the use case anymore. Common example of how silly this is is functions that have a special case for handling lists of length 1 by allowing the object to be passed in without being part of a list, eg Union[List[Foo], Foo]. Now you need extra code to handle that special case in your function and the type annotation becomes more verbose. Much easier to just enforce List[Foo] as the only way to call your function and let the caller call you function as do_stuff([x]) if they only have a single item x. The IDE will complain instantly if you try to call it as do_stuff(x) so there is no risk to accidentally call it like that.


Most of what you say I agree with. But it is actually very easy to construct a __init__ that handles a very flexible parameter list. That style is well represented in the standard library. And I find that writing a beefy constructor simplifies parameter validation in methods.

But certainly, it doesn’t remove the need for good test coverage. Now, if you want to argue for less testing, that is a different pitch.


Possible, yes. Easy and straightforward, no. Casework and duck-type checks over args and *kwargs are not easy to maintain going forward, especially for a non-expert in Python.

Factory methods are usually better, and I believe what the more modern parts of the standard library have tried to use, but they don't work as well for this specific case.


> Casework and duck-type checks over args and *kwargs

Once you start doing this in python -- beyond any low complexity usage -- you're better off capturing your arguments in their own object and passing this to the function as a single argument. This object can then enforce whatever you want, and your function can now be a class method.

And then you can build a factory for this object that accepts a few args, maybe some keyword parameters. But, once that gets reasonably complex, just rinse and repeat. It's objects, methods and factories all the way down. And unlimited billable hours.


> which will raise TypeError

At runtime. In production. A compiler would catch the problem at build time, never causing an error in production.


Maybe you missed this part:

> So of course you actually need to exercise the code also, which is where the hypothesis package comes in.

These are tests you should be doing anyway. Type checking does not excuse poor test coverage.


> A compiler would catch the problem at build time

And a nice IDE/code editor would catch the problem as you're typing it


Not in a dynamic language. Finding all possible inputs to your method that’s calling float() on its Arguments in a dynamic language is akin to solving the halting problem.

Also, error messages in an IDE can be ignored. Compiler errors can’t.


Why didn't you just rewrite it? If I have 500 lines that is a mess. It's getting rewritten. If you don't understand it enough to rewrite it, then you probably don't understand it enough to even maintain or change it. In my opinion, first thing I will do is dissect it till I understand it, rewrite it and move on. 500 lines of code ain't shit. I will kill the monster before it grows.


> The worst, buggiest, least maintainable code I've ever dealt with was a 500ish line (heavily tested!) untyped Python file at my last job. Caused more trouble than all the Java and C++ and Go combined.

That cannot be because of missing types. If you make just 500 lines python unmaintainable you're just a terrible programmer! In it's core it cannot be a type problem. And that is my main point here; there is a hype going on in the dynamic languages to add types, as if it solves everything. IMHO it's not that simple.

To avoid unnecessary explanation about the value of static typing; I am not against, I'm doing c++ and it serves me well.


The code in question was somewhat experimental, didn't have a clear owner and was modified by a number of people fairly frequently. There would've been much less room for sloppiness and shortcuts (let me just stick this extra field on this object) in Java, C++ or Go.


At our job we use a homebrew C++-like language. Now some people are planning to rewrite a core part of the problem to be completely dynamic, only using key-value to control the behavior of a large number of things. There is no namespaces and the value is an Object (base class for all classes), so you always have to cast.

The absolute horror it will be to debug this mess when people starts to use it, my god.


> The worst, buggiest, least maintainable code I've ever dealt with was a 500ish line (heavily tested!) untyped Python file at my last job. Caused more trouble than all the Java and C++ and Go combined.

Another problem with Python is that the culture of testing is definitely not as strong as in Ruby so you won't even get that to compensate the untyped nature of the language.


And yet another problem is that while Ruby classes require you to whitelist attributes (e.g. with attr_accessor) that will receive publicly-accessible getters/setters, Python classes by default allow the caller to "tack on" any instance variables they desire. (I've rarely seen __slots__ used in the wild, for instance.)

So the type of almost any class is really more like Struct{WhatYouThinkTheClassIs; ArbitraryDict;} ... and the temptation to misuse this, say for instance if you don't want to add arguments to function_used_lots_of_places(some_object), can be huge. And more generally, this openness means that there may be discrepancies between what's unit tested for a class, and how that class is used in the wild.


I'm still astonished that large companies run large systems on Python. I don't doubt that they're making the right decision, but it's a testament to how powerful inertia in large engineering systems is,given how incredibly horrible Python is for writing maintainable, sustainable multi-developer code. The most unfortunate part is that every engineer on a Python system has to be much more talented and disciplined to keep a workable system than they have to for statically typed languages (esp memory-safe ones), and yet one of the main attractions of choosing Python is chosen is that any rando can be nominally productive in it, which is helpful for talent-starved small companies.


The early portion of my career was python. Coming from OCaml/Haskell. I couldn't really deal with J2EE.

But I've seen a frustrating trend recently. Where companies on languages like C#/Java. With a massive code base are moving to Python. I often get recruiter calls to lead a new python team, transitioning away from (Java/C#/etc).

The sole reason, is it takes to long to develop a feature in the legacy language. Which is resultant of outdated software and practices. I generally try and steer them towards CI/CD and newer lighter frame works. But someone way up heard python was the hot new thing. Now all critical back-end services must be in python.

To add to this I pushed for type hinting. But type hinting will "slow down developers". Part of the problem to me, at least. Is the person dictating development frameworks, languages, etc. Isn't really a coder, and heard about thing X at some conference.

I like python, but it's an infrastructure quick scripting language. Additionally a lot of the good python devs I know shifted over to tensor flow and machine learning.


Counterpoint: I've worked on a production >100-KLOC >10-programmer Python system, and lack of types was not an issue at all.

For the past week I've been updating a (much shorter) solo app, written in a statically-typed language, and having a bear of a time getting the compiler to stop throwing false type errors at me. Yes, it's the stable release version of the compiler. That has never stopped any compiler from shipping with bugs. These type inference systems can be pretty complex.

Then again, I seem to run into more compiler bugs in general than anyone I know. How do you all do it?


How are you sure typing would have made a difference to that piece of Python? Are you sure there is no troublesome code written anywhere in Java or C++?


IMO type checking is to python what bounds checking is to C.


> IMO type checking is to python what bounds checking is to C.

Interesting analogy, but in my experience it doesn't hold up.

If you have a Python typing bug, it will crash your program almost immediately and you can quickly catch it and fix it. In contrast, C boundary checking issues often come up in relatively rare corner cases, and when they do occur, they often do not result in an exception / error especially if the memory is already paged and set to zero.

In production code, boundary checking errors in C occur all the time, but Python type bugs are pretty rare.


I think he's talking specifically about the Java and C++ code that accompanied that Python file. I'm sure he's seen bad Java and C++ :)


I've seen some disgusting Java and C++, but that company had pretty high engineering standards so the Java and C++ was always good. The Python was _supposed_ to be good, too, but 20+ devs making changes to this one config generator script meant people were constantly making their own (and breaking each other's) assumptions about how the code should behave.


At that point the first person to write good unit tests gets to specify how that part of the program is going to run. Though, really it sounds like you guys need a design pattern to address everyone's needs that everyone can agree to.

People can change unit tests. The benefit of this, is it's a kind of documentation. People can see the tests and know how it _should_ work, or they can not read them and if they create errors, they'll find out the hard way. Unit tests don't make a specification static, but they do reduce chaos.


This was a few years ago. The chaos was partly because of a lack of ownership: one team owned inputs, the other owned outputs, and there wasn't a clear spec. The whole pipeline was pretty experimental and people made up new semantics as needed, and were happy to change failing tests (or just added untested codepaths behind boolean flags in existing functions). Compile-time type checking would've enforced a greater degree of rigor about modeling what was happening and made clear all the ways existing code could be affected.


On a near daily basis, I thank you guys for writing mypy. I have a personal project spanning 2 years or so that I used mypy on 3 months back, and it has been a pleasure to work with ever since.

Just a few days back, I had had to refactor one of the core classes used everywhere and mypy told me exactly what to fix. At that point I remember wondering what I'd have done without mypy.

Thanks a ton.


Genuinely curious: how does a project like Dropbox grow to four million lines? In my (albeit naïve/inexperienced) mind, I could see how a file syncing app could grow to several thousand lines (even tens of thousands), but what all is in those four million lines? Is it a lot of UI code? Backwards compatibility for older data models? Building components themselves instead of just `pip install`ing?


Let's just dig into one aspect of a file syncing app: storing the files at Dropbox scale. Without spending too long thinking about it, I'd say we need:

  * An API to request files/upload files
  * Authentication/encryption (how do we ensure DB employees aren't arbitrarily reading data on servers?)
  * Service that shards data and handles multi-region replication
  * That entails multiple datacenters (how much code does it take to keep a DC running?)
  * Automated backups to cold storage
  * Automated restoration + testing of cold storage volumes
  * Soft + hard deletion (deleting hot/warm/cold storage volumes reliably)
  * Error handling (retries, host errors, network failures, filesystem corruption, finding data when a host dies)
  * Fail-safes like blocking requests when hosts can't handle them, shedding load, etc.
I'd also add that at this scale, very few libraries start handling the types of problems you have. For example, there might be a memcache library that makes writing queries easy. However, when you have thousands of memcache servers globally you can't just hardcode IP addresses anymore. You know have to write your own custom 'service-service' that lets you look up what host you should talk to for something.


I could imagine a list 10X that size that still does not need even 400K lines of Python code, much less 4M.

I agree that people generally underestimate the lines of code needed to operate systems at scale, but having used dropbox, I'd still say that's excessive. Note, this is just the Python code, not the UI stuff or the golang or Rust. I've been in multiple 1M-10M line codebases at scale and I just cannot fathom with how with their product simplicity (not engineering simplicity) they could be at that size with a language as expressive as Python.

My guess is this is generously counting a lot of forked libraries. Ungenerously, it makes me think there's a lot of NIH syndrome.


Per the blog post, it's both the client code and server code. The server code is >75% of the annotated Python. Which makes sense, given the amount of stuff you have to do to run one of the world's larger distributed file system with tight SLAs (and auth, and your own data centers, etc).


Client support for every random configuration you can imagine (Windows XP in Arabic for example) and a ton of server side performance optimization.


MyPy has worked very well for our small team of backend developers.

It's already production-ready in my opinion, but it still lacks some modern type-system features that you may be used to from other languages. Ironically, for instance, before Protocols were introduced (and they're still not part of Python core), there was no way to express structural subtyping in MyPy (for a duck-typed language!). And I'd argue they've made the same mistake (and a worse one in some ways) with TypedDict, which is a strict/nominal set rather than an a structural one, making it impossible to use on many real-world collections that have key-value pairs that you want to forgo typing for whatever reason.

Still, Python with MyPy is a huge step up from Python without, and mypyc looks interesting as well.


Using mypy with http://attrs.org for all my classes has been a revolution in my Python codebases. I can't emphasize enough how big a difference it makes in readability and correctness.

    @attr.dataclass
    class Person:
        name: str
        age: int


What advantage is there to using attrs instead of the new dataclasses module in the standard library?


From "Why attrs"[1]:

PEP 557 added Data Classes to Python 3.7 that resemble attrs in many ways.

They are the result of the Python community’s wish to have an easier way to write classes in the standard library that doesn’t carry the problems of namedtuples. To that end, attrs and its developers were involved in the PEP process and while we may disagree with some minor decisions that have been made, it’s a fine library and if it stops you from abusing namedtuples, they are a huge win.

Nevertheless, there are still reasons to prefer attrs over Data Classes whose relevancy depends on your circumstances: attrs supports all mainstream Python versions, including CPython 2.7 and PyPy.

Data Classes are intentionally less powerful than attrs. There is a long list of features that were sacrificed for the sake of simplicity and while the most obvious ones are validators, converters, and __slots__, it permeates throughout all APIs.

On the other hand, Data Classes currently do not offer any significant feature that attrs doesn’t already have.

attrs can and will move faster. We are not bound to any release schedules and we have a clear deprecation policy.

One of the reasons to not vendor attrs in the standard library was to not impede attrs’s future developement.

[1] http://www.attrs.org/en/stable/why.html#data-classes


My own experience with dataclasses vs attrs:

`dataclass` primarily just provides a faster syntax for defining a simple struct-like class. For that alone I've found it tremendously useful.

At the same time, sometimes you do need more functionality around a "struct-like" class, that that's where attrs comes in (e.g. I use dataclasses/attrs to wrap configuration objects, which mostly just hold values but occasionally need a little more logic). Even then, I often end up extending the basic dataclass with some of my own common methods (e.g. serialization).


"They are the result of the Python community's wish to have an easier way to write classes in the standard library that doesn't carry the problems of namedtuples."

What are the problems of namedtuples?


IIRC, one of the biggest complaint is about performance. namedtuples are not known to be fast, but I think performance has gotten better in more recent Python versions.

This is just what I recall from reading PEPs, the email lists and release notes. My recollection may not be entirely accurate.


See the why-attrs link above.


You can gain a fair memory consumption improvement by using slotted classes. The standard namedtuple and attrs use them. However, dataclasses do not. So, you can have both ergonomics and slotted classes by using attrs.


Attrs similarly changed my life and I am so happy that it’s spiritual successor dataclass has been included in python 3.7!


It's a successor in that it came afterwards, but it's actually less powerful. All my projects have dependencies, and I make sure attrs is the first one.


How is it less powerful, for those of us who know little about either?


See my longer response above.


The article mentions PyPy wasn't fast enough on their code, so they wrote a whole "new language". I wonder if they tried contributing some relevant performance improvements to PyPy. I ask because in my experience PyPy can make a massive difference if I'm using it on CPU-intensive code, and it'd benefit everyone to have a faster PyPy.


Dropbox's implementation of python was pyston: https://github.com/dropbox/pyston

But I think it's dead now: https://blog.pyston.org/


> We thought about running it automatically on every test build and/or collecting types from a small fraction of live network requests, but decided against it as either approach is too risky.

I wonder what's too risky about it? Did you try it and something broke? Instagram's MonkeyType seems to think it works ok in production. https://monkeytype.readthedocs.io/en/stable/stores.html


Treating “all the types we happened to see at runtime” as “all the types allowed” automatically is I think what they meant as “risky”.

MonkeyType seems to record the answers and then let you decide (via apply) if you want to agree. I’m a little surprised more people didn’t use this approach, but it probably makes for fairly clumsy type sets that the human is better off saying “Eh, I’ll just document it correctly”.


How is python's added typing compared to TypeScript? Is it expressive enough to completely supplant traditional Python or is it just something you add in sometimes to help out a little?


If I'm not mistaken, all of the types are akin to comments: completely ignored by the interpreter.


So there's the same situation as with TS where you can't do runtime reflection to get type annotations? That seems frustrating. The one really rough edge I've seen in TS is type guards. Not the end of the world, obviously, but still kinda gross coming from Java.


You can get annotations for functions and classes with `typing.get_type_hints()`.


You can get type annotations at runtime. And you can use this for validation with something you roll yourself or with 3rd party packages. I've used pydantic [1] before and found it quite nice.

[1] https://pydantic-docs.helpmanual.io


Pretty much all of my Python is typed with attrs and mypy. It's great.


Do you have editor integration by chance?


There are integrations for all the major editors.


it lacks some nice features like built-in structural subtyping (which is basically the default in TypeScript) - see my other post. A few things are a little clunkier because they're being added to an existing language rather than the language, and having to import things just to get Optional and generics is kind of ugly. But functionally, it's pretty much all there.


This is an incredible story! Love hearing how academic projects can evolve into success stories like this. I'm looking forward to an update a few years from now.


The only shocking thing here is that no one stepped in earlier. It's not like Java/C# are radical new technologies in the online space. Obviously statically typed languages are not a new concept either. I'm just aghast at this culture. It's actually quite shocking that this debate is a real one taking up a large amount of airtime in the industry.


  > I was trying to find ways to make it possible to use the
  > same programming language for projects ranging from tiny 
  > scripts to multi-million line sprawling codebases, 
  > without compromising too much at any point in the 
  > continuum. An important part of this was the idea of 
  > gradual growth from an untyped prototype to a battle-
  > tested, statically typed product.


It’s even more shocking that the solution is to shoehorn some half assed typing to Python instead of dropping it altogether in favor of something suitable.


The article show's several graphs of Lines of Code over Time. Isn't this an outdated way to measure anything in software?

"Measuring software productivity by lines of code is like measuring progress on an airplane by how much it weighs." - Bill Gates


But they aren't measuring software productivity by lines of code...


> The article show's several graphs of Lines of Code over Time. Isn't this an outdated way to measure anything in software? "Measuring software productivity by lines of code is like measuring progress on an airplane by how much it weighs." - Bill Gates

Lines of code is not a good measure of productivity, but it's a relatively decent measure of complexity. Bad developers can write many lines of code that adds little productive functionality, but still increases the overall code-base's complexity.

A 100k LOC code base is probably not 100X more functional than a 1k LOC code base, but it is very roughly 100X more complex.


Where did you get the idea that they’re measuring productivity by lines of code? I think you’ve imagined that.

Nothing in the article suggests they’re measuring anything but the scale of the problem.


It’s quite a decent way to show how much you use a language in an organisation.

With this much Python, Dropbox is in a position where integrating MyPy with Bazel is worth the trouble.


About 4 million lines of Python code is just a bad decision from the start. Now they are trying to fix it with types, but IMHO the fault is not in the language. They just should have started out with a statically typed language that scales better.


Yeah, I'm sure Dropbox's founders remember and regret that fateful day when they said to each other, let's write 4 million lines of Python to get this fledgling business idea off the ground!


I feel mypy has been a big help in the correctness and approachability in my code I write. My big issue is that it is too easy for `Any` to (implicitly) show up, making it so parts of my code are (silently) not checked. I wish there was a good way to spot this; maybe I need to dig more into the docs.


    mypy --help 


    Disallow the use of the dynamic 'Any' type under certain conditions.
  
    --disallow-any-unimported
                              Disallow Any types resulting from unfollowed
                              imports
    --disallow-subclassing-any
                              Disallow subclassing values of type 'Any' when
                              defining classes (inverse: --allow-subclassing-
                              any)
    --disallow-any-expr       Disallow all expressions that have type Any
    --disallow-any-decorated  Disallow functions that have Any in their
                              signature after decorator transformation
    --disallow-any-explicit   Disallow explicit Any in type positions
    --disallow-any-generics   Disallow usage of generic types that do not
                              specify explicit type parameters (inverse:
                              --allow-any-generics)


the article mentions that the desktop client app also uses python, is dropbox still using qt?

the reason I'm asking is I remember a blog post from earlier this summer announcing the new desktop app which looked very webtech-ish.


This is impressive. But IMHO, anyone starting today should just use Rust from the beginning, and avoid having to endure all of the problems for years and then taking on a big HN-worthy post about 4M lines.


Why do so many people think "just rewrite it in Rust" makes sense to post on every single topic about a dynamic language, even though nobody ever does it because it doesn't actually work in reality?


That's not what he said? You live up to your name though.


Maybe if you're Dropbox, Rust is an option, but Rust is much, much more complicated than Python and not nearly as approachable - especially for beginners.


I'd argue that how complicated a language is shouldn't be a factor for a company at that scale. If it's the right tool for the job, and I'm not arguing that it is, then they have the ability to hire engineers capable of using it.


I dont know the experience of others with this, but in reality is not that easy to have people with enough brain power or compromise for languages like C++ and Rust.

Sure theres "enough for the world", if you sum everybody, but i bet that even a company of the size of Dropbox would have a hard time hiring everyone they need if the complexity of the language is the same as Rust or C++.

For instance, if you read the Go manifesto to why the language was created, you can read between the lines that C++ getting in the way where enginners were fighting hard to deal with language complexity.

So the same way as Java before them, they created a language for the "average programmer". And you can see how the language is very pragmatic and how they fight to no put more complexity on the language.

By the way, i know its a personal opinion, but i think Rust will not be a great fit for the cloud backend exactlya because of this kind of thing. It will be hard to create codebase as complex as the ones in Java, because there will be much less man-hours available.

Maybe if generations are getting smarter, IDK, but needing to rely on a big head-count of C++ or Rust enginners for any company, i bet they will have a hard time to fill all the positions they need.

Theres much more C++ enginners than Rust right now, and companies have a hard time hiring them (and i bet that for Rust it will be no different giving its complexity).


> if you read the Go manifesto to why the language was created, you can read between the lines that C++ getting in the way where enginners were fighting hard to deal with language complexity.

My understanding was that the push from C++ to Go was largely motivated by long C++ compile/linking times. Go is designed to compile large code-bases much faster than C++. I've experienced this myself, it's pretty frustrating to wait 2-5 minutes for a C++ app to compile / link just so you can debug it for 30 seconds and start all over again.


> I've experienced this myself, it's pretty frustrating to wait 2-5 minutes for a C++ app to compile / link just so you can debug it for 30 seconds and start all over again.

That's quaint. When I first started working in C++ professionally, we were at +16 hours for a clean build and link of just one of our systems (circa 2004). Over the years, reduced the amount of code (without sacrificing functionality), shared common code (object model) between client and server (went from about 2M LOC to about 250k LOC), reduced the number of external dependencies, introduced precompiled headers on both windows and Linux, and a clean build was down to about 30 minutes. An incremental build and link could be less than 30 seconds, depending on the scope of the change. But, yeah, C++ leaves a lot to be desired in terms of edit, build, test, repeat cycles.


But this is a by-product of their initial motivation.

I mean, how could they know their link times would be that much better?

I know that Pike et al. worked on compilers for Inferno, and the Ken´s C compiler used some tricks to know beforehand they could make it compile and link very fast, but they couldnt know if the language they were designing would be as fast (to compile) as C or Limbo.

They could say confidently: "we could do better", but they would need more pragmatic motives, even to convince Google to support that effort.

And i bet Google was struggling with C++ enginnering, specially within the newcomers on the team.

I think, both things are true, but developer ergonomics play a very important factor into the why of a language and where it fits in the scheme of things.


But why rewrite a 4M LOC system that works in a new language, just because of a new shiny? How do you justify that from a business standpoint? Especially when your current engineering staff are already experts in the currently used technology, but likely novices or mediocre in Rust? Do you freeze new features during the rewrite? Do you develop new features in parallel? Rewriting, just because, Rust, is a waste.

Sure, you can hire new talent that knows Rust, but how long for them to pick up the relevant domain knowledge? How long for the existing talent that has the domain knowledge to become equivalently proficient in their current tech stack?

Plus, with a rewrite, you risk falling ill to second system syndrome.

Economically and from a practical engineering standpoint, it seems to me that Dropbox is taking a very pragmatic approach. Gradually make working with the exist code easier rather than throwing the baby out with the bath water.

I'm not hating on Rust, but this fervor to rewrite everything in Rust is obnoxious and often short sighted, in my opinion. If you're talking brand new, green field development, by all means, it should be considered.


> I'd argue that how complicated a language is shouldn't be a factor for a company at that scale.

I'd agree with you re Dropbox, a relatively "enlightened" modern software company. But they are the exception, not the rule. Most companies, big and small hire the cheapest developers they can find. I'm not talking about silicon valley tech companies, but think e-commerce and corporate sites (Nordstrom, General Motors) and old school companies (e.g., Goldman Sachs, power utilities).

These companies don't care about the "right tool for the job", they care about the right people for the job. If the "people" are adept at antiquated technologies, then they'll still pass muster.


  shouldn't be a factor for a company *at that scale*
Exactly.


How so? Rust is much, much less complicated than C++, a language that's routinely taught to beginners. Python is far more complex than either of those, especially once you account for the incidental complexity in whatever "packages" you're interfacing with - Rust simplifies a lot of that stuff without giving up on performance.


I'm fully in support of the beginners near coming anywhere near my data storage and synchronization.


Uh, no.

Rust crates are still full of the type of bugs that Python libraries killed years ago. Garbage collection overall really does make things easier. And Java still does concurrency better than practically everything else out there.

If you are doing systems programming, go for Rust instead of C++. Maybe even Rust instead of C. But, I would reach for Python, Ruby, Kotlin, Clojure or even modern Java before Rust for most non-Javascript projects.


Agreed, any strong typed AOT/JIT compiled language should do the job.

As for systems programming, I would argue that Rust still isn't an alternative to C++ in several deployment scenarios that many of us care about, although it should eventually get there.


I'm not familiar with Java's concurrency. In Python I'm excited about the idea of "structured concurrency", embraced by its wonderful Trio library. https://trio.readthedocs.io


java.util.concurrent is sheer utter brilliance condensed to code.

If you're doing concurrency, you should look at how java.util.concurrent does it. And, then, if you aren't doing it that way, you should start asking really hard questions.


Such a strong wording lead me to check this library doc but I couldn't find anything in there unexpected or that looked particularly original, but only classical message queues, semaphores, futures and locks. Could it be that I looked at the wrong thing?

Could you be more specific about what triggered such an enthusiastic description?


Reference here: https://docs.oracle.com/javase/8/docs/api/java/util/concurre...

Take a good look at things like ConcurrentLinkedDeque or ConcurrentSkipListSet and then look at what guarantees they do and do not make and think about the rationale behind those decisions.

And all you have to do in java is create the collection, hand the reference to multiple threads, and IT JUST WORKS like a normal Java collection.

You do NOT want to be using semaphores and locks directly if you have java.util.concurrent at your disposal. Look at the "Concurrent Collections" section:

    Most concurrent Collection implementations (including most Queues) also differ from the usual java.util 
    conventions in that their Iterators and Spliterators provide weakly consistent rather than fast-fail traversal:

        they may proceed concurrently with other operations
        they will never throw ConcurrentModificationException
        they are guaranteed to traverse elements as they existed upon construction exactly once, and may (but are not guaranteed to) reflect any modifications subsequent to construction.
These are remarkably difficult properties to get correct. Then you have to think about fairness vs progress (the java.util.concurrent folks have thought about this a LOT).

That library is something I sorely miss when I'm operating in another language.

You should also dive into the code itself. Sometimes you'll look at a function and marvel at how much work it really takes to get something right. And sometimes you'll look at a function and wonder how it could possibly be so simple.


I will have to look at the implementation some day, but I think I am starting to see what you mean with the API.

Is the typical implementation of those days structures relying on many small scale mutexes to avoid contention, or RCU techniques ?


Mostly compare-and-swap mechanisms at critical points so that everything is always consistent (for very specific definitions of consistent).

I think there are alternative paths when contention starts becoming too high.

It's been a while, but the code is the real reference. You can go back and look at the mailing list archives as well.

http://cs.oswego.edu/mailman/listinfo/concurrency-interest


You’ll like Kotlin’s concurrency model then! It not only allows concurrency, but also parallelism, with rich language support. It is really pretty great, check it out!


> Rust crates are still full of the type of bugs that Python libraries killed years ago.

That's an interesting claim. What do you mean specifically?


Whenever I pull a crate, I invariably have to file a bug because I don't have the exact OS, OS version, Rust version, and crate version that the author develops on.

I rarely file bugs on Python packages no matter how obscure my setup is.


Is that just a forward compatibility only? As in you're trying to compile on stable what is only available on nightly? Or are there actual backwards compatibility issues?


Mostly it's OS combos: Package X has issues on the latest Windows 10. Package Y runs on Ubuntu but not RedHat and fails on the latest release of Debian but not the prior one. Package Z crashes on OS X any older than yesterday.

Now that Rust stable is actually reasonably solid, I just ignore crates that demand nightly.


If you're starting now, you could use typed Python on PyPy from the beginning.


> If you're starting now, you could use typed Python on PyPy from the beginning.

And if you started 5 years ago, you're now likely spending half of your time over the next few months/years porting your code from Python 2 to 3. What a disaster.


In what world does it take years to port a Python 2 project to 3? This FUD gets tiring after a while.


Having never used Rust: how's the ecosystem around it like for maintaining it on production servers? Are there equivalents of Java Mission Control and `jcmd`?


The ecosystem is good and growing, but not as extensive as java, of course. We use generic cloud monitoring/SRE tools (think statsd, not jcmd, for example). From prior experience, I also think JVMs somehow tend to require more of this kind of tooling, compared to Rust - or, for that matter, other languages & runtimes.


I'm curious how much Java needs the tooling in an all else equal situation (totally possible 'cuz Java is _old_) vs how much Java has all this cool tooling that enables/encourages people to do things that aren't possible in Go but which require the tooling.

I've mostly used the Java, C++ and Go in my career, and it's pretty amazing to me that I can write write safe, readable code (like Go) on top of high-performance libraries (like C++) and step through code on a prod machine with a remote debugger, get live performance metrics with a single CLI command, etc.


Not in the same way. Since there's no significant runtime behind rust, there's no specific way to query it in an abstract way. On the other hand, native code means that you can use any common way to query the system. Anything you'd use for C or C++ will work as well. Profilers, stack querying, monitoring the process itself, user-level probes, etc. will work just fine. There's lots of tools that will work for this.




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: