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

The Rust designers might have known that other memory-safe programming languages had existed, but certainly they were not familiar with them.

Otherwise Rust would not have inherited a ton of obsolete and useless features of C.

For example, when I have seen for the first time a "Hello, world" written in Rust, I have been appalled to see that in a language designed in the 3rd millennium one still has to specify whether function parameters are passed by value or by reference, which is the wrong thing to specify about function parameters, exactly like specifying in old C language whether a local variable was "register" or "auto" was the wrong thing to specify. (The right thing to specify about function parameters has been established in 1977, almost a half of century ago, by DoD "IRONMAN", which has been implemented only in a few languages, the most important being Ada. In my opinion, most difficulties and complexities of C++ have been generated by its failure to comply with "IRONMAN".)

In my opinion, attempting to design a new programming language without first studying carefully the history of programming languages, in order to know well the many good and bad choices that have already been used in various programming languages and to understand which have been their advantages and disadvantages, is an amateurish approach that cannot result in real progress.

Most recent programming languages have been built around some innovative great idea that makes that programming language better than the previous in some respect, e.g. Rust for compile-time checking of memory accesses, but because in the other areas of the language the best previous features have not been chosen, no language is uniformly better than older languages, all of them are better for something than older languages, but worse than older languages for other things, which makes the choice of a programming language very frustrating.




> The Rust designers might have known that other memory-safe programming languages had existed, but certainly they were not familiar with them.

Graydon is incredibly knowledgeable about programming languages.

Just because you have certain opinions about language design does not mean others do not know things.

Speaking of knowing history, Ada implements STEELMAN, not IRONMAN.


I'm not sure I understand your argument.

Rust is very much designed as a low-level language. If you want a higher-level language with similar guarantees, OCaml, Haskell, F# or Scala do the job very well already.

The mechanism of argument passing has its uses in terms of both performance control and avoiding unwanted aliasing, something that not many languages do properly (not even Ada, iirc).

It's not the right thing to do for all languages/applications, but I believe that it's the right thing to do for Rust. Do you have a better design in mind?


What they want is in/inout/out, with the compiler determining if the semantic is better implemented by value or by reference.

In Ada, there are also requirements that some types are always passed by value or by reference. And as of Ada 2012 there's a way to force the "always pass by value" types by reference.

A while back someone wrote a post comparing Rust to the sort of design your parent prefers https://web.archive.org/web/20241009004034/https://jedbarber... I remember it being... fine.

The short of it is, Rust doesn't do great because STEELMAN wants subtyping and contracts (though I see contracts may be coming to Rust...) as well as return values instead of exceptions.


Oh, I thought they were advocating for something new. Yeah, the split in/inout/out is nice, but doesn't sound particularly better than what we have in Rust. In particular, in my book, return values instead of `out` clearly win in terms of readability.

Also, yay for contracts in Rust :) Looking forward to seeing them proven by model-checkers, too!


> attempting to design a new programming language without first studying carefully the history of programming

Rust's original designer credits these languages as the inspiration: http://venge.net/graydon/talks/intro-talk-2.pdf

Mesa (1977), BETA (1975), CLU (1974), Nil (1981), Hermes (1990), Erlang (1987), Sather (1990), Newsqueak (1988), Alef (1995), Limbo (1996), and Napier (1985, 1988).

The original Rust compiler was written in Ocaml.

> has to specify whether function parameters are passed by value or by reference

You specify whether arguments are borrowed or moved, and whether the access is shared or exclusive. This is not an implementation detail, it's an API contract that affects semantics of the program. It adds or removes restrictions on the caller's side, and controls memory management and thread safety.

People unfamiliar with Rust very often misunderstand Rust's borrowed/owned distinction as reference/value, but in Rust these two aspects are orthogonal: Rust also has owning reference types, and borrowing types passed by copying. This misunderstanding is the major reason why novices "fight the borrow checker", because they try to avoid copying, but end up avoiding owning.

There are different possible approaches to achieving similar results for argument passing, but Rust prefers to be explicit and give low-level control. For example, Mutable Value Semantics is often cited as an alternative design, but it can't express putting temporary loans in structs. The syntax needs a place to declare lifetimes (loan scopes), as otherwise implicit magic makes working with view types tricky or impossible: https://safecpp.org/draft-lifetimes.html


I agree about your greater point of learning about history being important, but very much disagree about your specific gripe. What Ironman says is:

  7.2.F. (7F.)  FORMAL PARAMETER CLASSES
  There shall be three classes of formal data parameters:
  
      input parameters, which act as constants that are initialized to the value of corresponding actual parameters at the time of call,
  
      input-output parameters, which enable access and assignment to the corresponding actual parameters, and
  
      output parameters, which act as local variables whose values are transferred to the corresponding actual parameter only at the time of normal exit. In the latter two cases the corresponding actual parameter must be a variable or an assignable component of a composite type.
  
  7I.  RESTRICTIONS TO PREVENT ALIASING
  
  Aliasing (i.e., multiple access paths to the same variable from a given scope) shall not be permitted. In particular, a variable may not be used as two output arguments in the same call to a procedure, and a nonlocal variable that is accessed or assigned within a procedure body may not be used as an output argument to that procedure.
These are very reasonable!

7I is important (in the mutable case), and solving the problem that bought forth this rule is the core idea behind Rust.

7F draws useful semantic distinctions: Output parameters are return values. Input-output parameters are mutable references. Input parameters act as a copy or readonly reference – which are equivalent if you can't mutate or observe mutation through readonly references.

Your complaint is that Rust draws a distinction between whether input parameters are implemented via a copy or a reference, but Rust draws many more distictions here – arbitrarily many so, because it's part of the type system. This makes the system both applicable everywhere instead of only in function parameters and allows expressing more different semantics.

For example I have a hard time seing how one would, while following the Ironman requirements, distinguish parameter whos type is "dynamic array of elements of T" from one of type "dynamic array of elements of type mutable reference to T". Likewise you can define a record that holds a mutable reference and an immutable reference, or define a new reference type (such as a reference counted pointer) without new language support.

I said that passing a copy and a readonly reference are equivalent. However, in Rust there are no readonly references. Rather, there are non-aliasing references, which allow mutation, and aliasing references, which by default don't allow mutation – but types can make use of interior mutability to allow mutation through aliased references according to they rules they need. For example you can access data protected by a mutex if and only if you hold the lock, which means that even if other references to the mutex exist, there are no other references to the inner data.


Just to be clear, not your parent, and not really a fan of STEELMAN. But.

> Input-output parameters are mutable references.

In/out can also be by value, it would copy any updates back after the call.

> Input parameters act as a copy or readonly reference

This is what your parent is getting at: the idea is that in/out/inout describe intent, not mechanism. The compiler chooses the mechanism for you.

I think in a language that's intended for lower-level tasks, describing mechanism is important. That said, outside of STEELMAN, there's an argument to be made for in/out/inout, and in fact, there's been some discussion over the years, for example, &uninit T as a sort of variant of out.

> For example I have a hard time seing how one would, while following the Ironman requirements,

You're right. STEELMAN updated this section to say

  7I. Restrictions to Prevent Aliasing. The language shall attempt to prevent aliasing (l.e., multiple access paths to the same variable or record component) that is not intended, but shall not prohibit all aliasing. Aliasing shall not be permitted between output parameters nor between an input-output parameter and a nonlocal variable. Unintended aliasing shall not be permitted between input-output parameters. A restriction limiting actual input-output parameters to variables that are nowhere referenced as nonlocals within a function or routine, is not prohibited. All aliasing of components of elements of an indirect type shall be considered intentional.
Ada has "access types," which are pointers. You can declare them as aliased or not. Via this mechanism, you can pass both an aliased variable as an in parameter, and an access type that points to it as an in out, and still modify the variable, even though it's in. This will give you surprising behavior, but it's not UB, so you get "oh hey that number is not what it should be" not "this means half your program is optimized away.

However, unlike Rust, Ada does do strict aliasing, and so you can also get miscompilations that way. It requires the moral equivalent of unsafe, though. See https://gcc.gnu.org/onlinedocs/gcc-7.5.0/gnat_ugn/Optimizati... for an example.

Ada does prevent data races, because it has a built-in task system, and that task system only allows multiple tasks to access "protected objects" that are basically RWLock<T> in Rust terms.




Join us for AI Startup School this June 16-17 in San Francisco!

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

Search: