Hacker News new | past | comments | ask | show | jobs | submit login

Personally I regard Rust as strictly less complex than most other languages (to use - not to learn). The space of programs you're searching through is smaller and more constrained (there are fewer valid Rust programs then valid Python programs, and we can make stronger statements about the behavior of a valid Rust program). Ergo, simpler. Internalizing those constraints so they aren't generally slowing you down takes an investment however.

Recently I had a vexing silent bug in a prototype, where I was intermingling strings and ints as keys in a dictionary, and thus not writing to the correct keys. Normally I would be more careful than that, but it was a quick and dirty proof of concept. Because this involved JSON deserialization, Python's modest static type checking couldn't catch it.

That just wouldn't have happened in Rust. (But of course, I wouldn't have had the particular ML libraries I needed, either, so Python remains the best option for this project.)




> The space of programs you're searching through is smaller and more constrained

People don’t write programs by scanning through the space of possible ones, so I don’t see why would that not be a hindrance, let alone help one.

Also, static types, or at least optional typing is at this point pretty much more common than dynamic typing, so I fail to see your example making Rust a better choice than “most other languages” for this reason.

Especially that the real strictness of Rust is from the ownership system, which allows basically only tree-shaped lifetimes, and that’s not a restriction like a program being type-safe, where non-type safe programs don’t make sense. It is an “arbitrary” constraint that has obvious advantages for not memory-managed languages, but is absolutely not a tradeoff I would take when not necessary, as random lifetimes are just as valid.


> People don’t write programs by scanning through the space of possible ones

I definitely search when I write programs. Do you write a program from start to finish and it works, or do you iterate and debug and sometimes find you've made a misstep & need to change your approach?

Python's optional typing wasn't capable of spotting my issue, that was my point. Note I didn't say it was a better choice. I said, "strictly less complex." I wouldn't make a claim that Rust is "better." That only has meaning relative to a set of requirements. (Furthermore, I mentioned Python was a better choice for my project.)

Tree shaped lifetimes are a very robust and easy to work with set of lifetimes. Of course you can add more degrees of freedom and write programs with graph lifetimes. That's fine, but it's pretty clearly more complex. I view allowing arbitrary lifetimes as something I don't want unless it's, well, absolutely necessary is too strong, but unless there's a compelling reason. It's not something I want to spend my complexity budget on, because it makes it more difficult to reason about and debug a program.


>Of course you can add more degrees of freedom and write programs with graph lifetimes. That's fine, but it's pretty clearly more complex.

How do you measure the complexity you're talking about here?

What if a program using graph shaped lifetimes was 10 times shorter than a program that had to use tree shaped lifetimes? Would you still say that the tree shaped program is less complex?


I don't think "number of tokens in the source code" is a great measure, no. More like, how many possibilities are there for the way this program will execute? How many potential branches are there? How many things can go wrong, and do we know if and when they will go wrong?

With Python for instance, it's just really difficult to say, because you may have arbitrary exceptions at any point. When writing long lived services, this is very significant; you need to handle all the exceptions that may be raised by you can't actually determine what that set is. I've had to read the source code of my dependencies on more than one occasion to figure this out, and still, you never get all of them before production.

With Rust this is much more manageable. Technically you could have an arbitrary panic at each step, but it's much less common to catch and handle panics than it is exceptions, and recoverable errors are handed via return values. Relative to Python, it's a breeze to figure out when errors may happen and what to do about them.

Similarly with my example before, what type are the keys in a Python dictionary? Probably the ones you meant to put there, but who really knows. What types are in the keys of a Rust dictionary? The ones you put there explicitly, full stop. There's no way for that to get screwed up or change from under you.


I kinda answered the wrong question here, that's how you would determine which program was more complex. Regarding whether trees or graphs are more complex - graphs are a superset of trees with additional degrees of freedom. Degrees of freedom are complexity. Graphs are more complex than trees and trees are more complex than lists, because we're drilling down into increasingly restricted subsets where we can make stronger and stronger generalizations.


I understand what you're getting at. A more restrictive language allows you to exclude a lot of things that cannot possibly happen and thefore you don't have to think about them any longer, which means less complexity.

But I'm still not sure whether this idea is sufficient to explain away the situation where you have to write a far longer program in order to work around those restrictions.

For instance, safe Rust cannot express linked lists in the usual way, i.e using pointers. So in order to write a linked list in safe Rust, you would have to reimplement some of the functionality that pointers provide using array indices (or similar).

This program would be very long indeed, plus it would cause many of the same safety issues that safe Rust was supposed to avoid. Essentially, what this program would do is create an unsafe memory management system in safe Rust.

I believe that the dominant factor for complexity of a program is its length. If you have two programs that produce the same output for the same input and one is 10 tokens long while the other one is 10,000 tokens long then the first program will always be less complex.

I say "I believe" because I'm a bit out of my depth here. These claims (mine and yours) clearly need formal proofs and some formal definition of complexity.


I actually don't think it's as difficult as people often suggest to write a doubly linked list in Rust (yes, I have done it and read Learning Rust by Implementing Entirely Too Many Linked Lists.). I think it's just surprising to people that something that's a CS101 data structure takes some advanced features to implement without locks.

But the thing is, you don't write C programs in Rust and you don't generally use doubly linked lists. (Linked lists turn out to be a very niche data structure that doesn't work well with modern CPUs anyway, but I digress.) You'd probably use a Vec or a BTree from the stdlib anywhere you're thinking of using a linked list.

So I don't think it's really the case that programs are significantly longer in Rust. Rust is more explicit, so you'll end up moving some things that existed as comments or documentation or just in your own head into code - that's a win that doesn't increase complexity, only exposes it. That program may look larger, but it's only because you can see it better.

It really depends on what those 10 tokens are doing. If I have a token that creates a new universe, seeds it with life, and creates the conditions for life to develop an optimal linked list - it might solve the problem in one step, but our atomic unit here is absolutely massive.

Similarly, if I compile a program to assembly I'll generally get many more tokens. But I can't really buy that I've increased the complexity of the program here.

I'm pretty satisfied with this understanding but I understand your desire for greater rigor.


>But the thing is, you don't write C programs in Rust and you don't generally use doubly linked lists.

The usefulness of linked lists is entirely beside the point. They just serve as an example for situations where additional restrictions can cause a program to be longer or more difficult to understand.

>It really depends on what those 10 tokens are doing. If I have a token that creates a new universe, seeds it with life, and creates the conditions for life to develop an optimal linked list - it might solve the problem in one step, but our atomic unit here is absolutely massive.

This example shows why I think that your claims lack a definition of complexity. You're now saying that the output of a program determines its complexity. I don't think that's a useful measure of complexity because it doesn't allow us to compare the complexity of two programs that produce the same output.


Not the particular output, no. It was how much was going on inside that hypothetical instruction, how massive of an abstraction it was, the unpredictability of the output. If you like you can think of it was a giant decision tree and imagine counting the branches to measure the complexity.


> Especially that the real strictness of Rust is from the ownership system, which allows basically only tree-shaped lifetimes, and that’s not a restriction like a program being type-safe, where non-type safe programs don’t make sense. It is an “arbitrary” constraint that has obvious advantages for not memory-managed languages, but is absolutely not a tradeoff I would take when not necessary, as random lifetimes are just as valid.

Rust's tree-shaped lifetimes are a big benefit for me, as someone who values not wasting RAM.

(Seriously. Why did I have to upgrade my computer to 32GiB of RAM when I haven't appreciably changed what I use it for since I bought it wth 4GiB of RAM and sized that amount based on intent to run a VirtualBox VM or two? ...JavaScript bloat. ...though I will admit that I also upgraded the CPU, RAM, and motherboard in the transition from 4GiB to 16GiB... but that was because a RAM socket went bad.)

I think of borrow-checker errors as "Wait a minute. Can you clarify what you intended here? Did you want multiple ownership? A copy? Was accessing it this far down a mistake?"


There are infinitely many Rust and Python programs, just as there are infinitely many English sentences. Search is a really weird way to talk about programming.


I'm gunnuh address this because I think these are interesting nitty gritty details, but note that these are metaphors expressed in a shared vocabulary that happens to be technical, let's not get too caught up in how many Rust programs there are, that's a more literal interpretation than I intended.

If you cap the number of tokens, the are fewer Rust programs. If you prefer, things that may be done implicitly in Python must be done explicitly in Rust - there are fewer valid choices when constructing a program; or equivalently, as you write a Python program, the options for your to proceed fan out faster.

Really we aren't searching the whole space of programs though. We understand that adding no-ops to a program doesn't move us out of the equivalency class of that program (unless we're actually using those for timing or another side effect, of course, but then they are no-ops in name only). We're only searching through programs that reasonably solve the problem at hand. Maybe that space is infinite, I don't think so.

I think of programming as being a fuzzy beam search (among other ways I sometimes think about programming). Your salience may vary, feel free to drop in a metaphor of your choosing there.


We can model programming as search, but programming as humans do it is more like design than search. I mean this in the qualitative and experiential sense.

I don’t sit in front of the screen as candidate programs flash by until I see the one that I want.

I also don’t edit by AST transformations from one valid program to another, like edges in a graph.

I really don’t see the possible program space as all that relevant.


> I also don’t edit by AST transformations from one valid program to another, like edges in a graph.

I don't really see how else you would edit a program? That's what you're doing when you type at your IDE, right?

But I think you're understanding how I think of it, feel free to take it or leave it. I'm not gunnuh die on a hill of insisting my way of thinking of it is right, it's just right for me.

> I really don’t see the possible program space as all that relevant.

In my example, I fell into a silently invalid state. In a more constrained environment, I wouldn't have. It's easier to go bowling with the bumpers turned on, and if your goal was to make as many strikes as possible (rather than to be sporting), surely you'd only ever bowl with the bumpers.

It's a Murphy's law thing. The more invalid states you have, the easier it is to get mired there.


Most programmers (including myself) are editing unstructured text and after that text is written the editor will tell them if it’s valid code or not.

Edit: I cannot reply, but there are structured program editors out there you may find interesting!


Yes, of course I do too, I live on the same planet, but that's an unstructured interface to changing the AST.

(You can reply to comments when the reply button is hidden by clicking the link to go directly to the post. I consider it a polite request by dang to consider whether a conversation is getting too heated, which I don't think it is here at all. Cheers, I'll try to check those editors out.)


I'd rephrase this as Rust enabling reasoning about program validity locally, while many other languages (regardless of type system, e.g. C, C++ and Python/Ruby/JS are typical examples) don't allow you to reason about program validity in localized increments. This promotes more modular designs, clearer interface boundaries and less "change anxiety".


> enabling reasoning about program validity locally

About certain few properties only. It is impossible in the general case.

I agree regarding “change anxiety” compared to dynamic languages, but that’s just static typing. Due to lifetime annotations leaking into API boundaries, the rest is not true though.




Consider applying for YC's Spring batch! Applications are open till Feb 11.

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

Search: