News for hackers. Stuff that matters.
Re:My experience rewriting Enjarify in Rust by steveklabnik on Monday September 19, 2016 @09:17AM (#12531168)
In a systems language, it's important to know if you're passing around things by value or reference. We follow the Python maxim here: explicit is better than implicit. This means &s to indicate where something is passed by a pointer, and * for dereferencing. This is the same as C++, we just do less magic coercions. We also tried to not make up too much syntax, and stick to "curly braces and semicolons" for most things. Even lifetime syntax isn't new, though OCaml uses it for a very different purpose than we do. (There also aren't any @s in there, unless I missed something? And . is the same as in any language: for method calls, and we made it _simpler_ than C or C++ here, by having . instead of . and ->.)

I agree that you can make a language without many syntactical constructs, but Rust has other goals as well. Language design is hard! There's tons of tension between various choices.

Re:My experience rewriting Enjarify in Rust by atombender on Monday September 19, 2016 @12:52PM (#12532798)
Rust is exceptionally noisy and terse. Easily worse than C++, in my opinion, which is known for being noisy.

Two things bug me in particular. Rust eschews readability by erring on the side of terseness — fn instead of something like func, for example, might save some typing, but does contribute to making visually denser.

The choice of snake_case instead of camelCase also adds to the noisiness, because there's no programmer font that aligns the underscore with the text baseline, so it's disruptive. Snake case is pretty aptly named; typographically, there's a lot more visual cohesiveness to camelcase. (As an aside, I never understood the lack of consistency here: the capitalized version of foo_bar should surely be Foo_Bar, not FooBar.) Python admittedly uses snakecase, but avoids looking too ramshackle by having otherwise very little noise.

Compare something in Rust (from OP's repo):

    fn type_list<'a>(dex: &'a DexFile<'a>,
      off: u32, parse_cls_desc: bool) -> Vec<&'a bstr> {
to Go:

    func typeList(dex *DexFile,
      off uint32, parseClassDesc bool) []BStr {
I'm not the world's biggest fan of Go, but they did nail the readability aspect. Nim is another good example which does it even better.

Of course, Go doesn't have lifetime annotations, but I can't understand who thought using ' was a great idea.

I wish more time was spent on the visual aspect of a language. I'm really liking Reason, Facebook's new syntax for OCaml, which shows that a language can be made fresh and friendlier through aesthetics alone. Elixir is cleaner than Erlang, but I think it went too far in emulating Ruby; instead of very simply aligning "def" and "end", for example, you now have "do ... end" everywhere).

Edit: Typo.

Re:My experience rewriting Enjarify in Rust by pcwalton on Monday September 19, 2016 @01:12PM (#12532953)
Your criticism comes down to wanting a different abbreviation for "function" and wanting camel case instead of snake case. I would suggest that it's not that we didn't spend time on the "visual aspect" of a language: rather it's that we made choices that were not your preference. We had to pick something and disappoint somebody; I apologize that that person was you.

> Compare something in Rust (from OP's repo):

As with so many of these comparisons, you're effectively comparing a language with GC to a language with safe manual memory management, and misinterpreting this as evidence of bad syntactic choices. (Same with Nim: Nim has GC.)

Look at the function signature when you remove the lifetime annotations:

    fn type_list(dex: &DexFile, off: u32, parse_class_desc: bool) -> Vec<bstr>
When you do that, there's very little difference between that and Go.

> Of course, Go doesn't have lifetime annotations, but I understand who thought using ' was a great idea.

Me. It was chosen because it's very visually lightweight.

Re:My experience rewriting Enjarify in Rust by atombender on Monday September 19, 2016 @01:36PM (#12533141)
> Your criticism comes down to ...

No, those were only the two most obvious, easiest-to-solve examples that came to mind.

With all due respect to you and your Rust colleagues (who have done a lot of impressive work), "we made choices that were not your preference" sounds a little condescending. These are my personal opinions, just as your counterarguments are opinions. We should all able to make technical arguments without resorting to that kind of rhetoric. Of course you made choices. Everyone here is entitled to disagree with them and explain why.

There's a false dichotomy between "hard-to-read syntax" and "no syntax at all" being implied here. It's reasonable to imagine that Rust could have had less terse, but still readable, syntax, even for things like lifetimes.

Re:My experience rewriting Enjarify in Rust by pcwalton on Monday September 19, 2016 @01:38PM (#12533153)
> We should all able to make technical arguments without resorting to that kind of rhetoric.

The subtext here is that these decisions aren't technical. They're pretty arbitrary: "func" vs. "fn" or camel case vs. snake case. There's no science here: there are lots of very successful languages with camel case and lots of very successful languages with snake case.

I certainly appreciate that you have specific reasons for your preferences, but I take issue with the idea that we didn't spend time on the visual aspect of the language.

> It's reasonable to imagine that Rust could have had less terse, but still readable, syntax, even for things like lifetimes.

Do you have a proposal?

There were long threads with different proposals back when this stuff was being designed, but none of them were particularly palatable. The verbosity with anything that wasn't as lightweight as possible turned out to be too much.

Re:My experience rewriting Enjarify in Rust by stcredzero on Monday September 19, 2016 @01:22PM (#12533037)
you're effectively comparing a language with GC to a language with safe manual memory management, and misinterpreting this as evidence of bad syntactic choices.

So a better comparison would be to Swift. I don't think Rust compares favorably with Swift either.

Re:My experience rewriting Enjarify in Rust by pcwalton on Monday September 19, 2016 @01:27PM (#12533069)
Swift doesn't have lifetimes either, as it's garbage collected via reference counting.
Re:My experience rewriting Enjarify in Rust by stcredzero on Monday September 19, 2016 @08:51PM (#12536080)
Swift isn't garbage collected. ARC is not GC. It's actually a form of manual memory management, with lots of help from the compiler. The way you structure your program does give particular objects a particular lifetime.

https://news.ycombinator.com/item?id=7813598

Re:My experience rewriting Enjarify in Rust by pcwalton on Monday September 19, 2016 @09:03PM (#12536146)
Reference counting is a form of garbage collection [1] [2], as I've explained numerous times before. It has been considered a form of GC since the original papers in the 1960s and I see no reason to revisit that definition now.

To track the lifetimes of objects, Swift programs perform operations (reference count manipulation) at runtime. That's because the Swift compiler does not know the static lifetimes of objects, unlike Rust. It can optimize away some reference count traffic, but this doesn't change the basic system: semantically, all objects are reference counted.

[1]: https://en.m.wikipedia.org/wiki/Reference_counting

[2]: http://www.memorymanagement.org/glossary/a.html#term-automat...

Re:My experience rewriting Enjarify in Rust by steveklabnik on Monday September 19, 2016 @01:51PM (#12533255)
Patrick already got into the subjectivity aspect here, so I'll leave that subthread there.

  > Rust eschews readability by erring on the side of terseness
We actually used to be more terse, then slowly expanded things until it felt right. At one point in time, "return" was "ret".

  > I can't understand who thought using ' was a great idea.
We had a lot of discussion about this before 1.0, and nobody was able to come up with a proposal that was clearly better.

  > I wish more time was spent on the visual aspect of a language.
We did spend a lot of time on this, and are continuing to. For example, the rustfmt team is just getting going, to nail down the last bit of style guidelines. Rust has had many different syntaxes over the years, and this is what we arrived at. See that other thread on subjectivity; I actually really like the "C with a bit of ML sprinkled in" that we arrived at. The only thing I don't like at all is the turbofish, ::<>, but I understand why it exists, it's almost never required, and it at least has a fun name to take some of the edge off. That said, I've also gotten less and less opinionated about syntax over time; semantics is far more interesting. As long as everything is consistent, I don't care as much about the specific details.
Re:My experience rewriting Enjarify in Rust by S_I on Tuesday September 20, 2016 @02:14AM (#12537484)
"C with a bit of ML sprinkled in" is IMO quite an understatement. ML is all over the place.

Personally i would have been happier if Rust had become "C with only the borrow checker and nothing else".But the community decided differently, and that's OK too.

I'm a low level guy (embedded and systems stuff) and what i would hate in our codebase is if look at line of code and find x numbers of concepts in it.

Nevertheless i applaud what you and the Rust community achieved, but my impression is that you'll be going the C++ way. Why? I follow the discussions of the Rust community about the future of the language, and there's always this "if we only add this one more feature" (for example see discussion about GC).

P.S.: For everyone saying "just use the features you need", no, this won't work. If they are available, people will (a)use them (see C++).

Edit: Typo.

Re:My experience rewriting Enjarify in Rust by rabidferret on Tuesday September 20, 2016 @01:47PM (#12541484)
> it's almost never required

Mate you should try Diesel or Futures. ;)

Re:My experience rewriting Enjarify in Rust by eridius on Monday September 19, 2016 @01:21PM (#12533022)
FWIW that Rust code can drop the last instance of `'a` since it can be inferred:

  fn type_list<'a>(dex: &'a DexFile<'a>,
    off: u32, parse_cls_desc: bool) -> Vec<&bstr> {
In fact, the only reason you need to write 'a anywhere there is to tell the compiler that the & reference and the lifetime parameter to DexFile are the same. If either of those two lifetimes didn't exist (e.g. if DexFile didn't take a lifetime) you could drop them entirely:

  fn type_list(dex: &DexFile,
    off: u32, parse_cls_desc: bool) -> Vec<&bstr> {
Re:My experience rewriting Enjarify in Rust by all2well on Monday September 19, 2016 @01:49PM (#12533233)
The difference with that go code is *DexFile is like Arc<DexFile>, and not like &'a DexFile<'a>. Yes, the go code is more readable, but it comes with additional overhead and data ordering constraints that the Rust code avoids.
Re:My experience rewriting Enjarify in Rust by cicero on Monday September 19, 2016 @02:18PM (#12533473)
I searched for Reactor and found Reason[1] instead. Is that what you meant?

1: https://facebook.github.io/reason/

Re:My experience rewriting Enjarify in Rust by lobster_johnson on Monday September 19, 2016 @02:25PM (#12533531)
Ah yes, sorry, it's Reason.
Re:My experience rewriting Enjarify in Rust by kevin_thibedeau on Monday September 19, 2016 @06:13PM (#12535215)
> there's no programmer font that aligns the underscore with the text baseline,

Pretty easy to fire up Fontforge and move the underscore where you want it.

Re:My experience rewriting Enjarify in Rust by unethical_ban on Monday September 19, 2016 @02:41PM (#12533656)
Hi Steve, I've been working on learning Rust from a beginner perspective (know some Python, never written a program with more than 500 loc). However, I do know what pointers are, and get the concept of stack and heap.

My comment is this: It is not immediately apparent that when using &var syntax, Rust is "auto-dereferencing" the variable to get the value. It took a moment to wrap my head around why

  println!("{}", &x); //this can show "5".
Rust's ownership/borrowing mechanism, where variables basically "die" if you don't pass by reference, is confusing until I read the FAQ and those three chapters in the Book a few times.

What I think the Book, or some other reference, needs is a "best practices for newbies" guide. Kind of like how we learn the atom is the smallest possible object in high school, I think it would be ok for a doc like this to tell newbies "You almost never want to pass a variable by value". It would help keep people from fighting the borrow checker.

Let me know if I'm crazy.

Re:My experience rewriting Enjarify in Rust by steveklabnik on Monday September 19, 2016 @02:57PM (#12533801)
Hey hey! So, this specific example has nothing to do with auto dereferencing in the language. It's that Display, implemented for &T, displays the value instead of the reference. This is what you want 99% of the time, so Rust is trying to Do The Right Thing here. If you want to print out the numeric version of the pointer, {:p} will do it. It's a bit tough to document specific implementations at the moment, so it's certainly not as clear as it could be. you're right.

( For reference, this implementation is here: https://github.com/rust-lang/rust/blob/master/src/libcore/fm... note that this is in a macro to do this for all of the traits at once...)

I'm actually re-writing the book right now, and one of the things I'm focusing hard on is the stuff around ownership and borrowing. The current chapters are too abstract, the new ones are focused on String/&str. You can find that here: http://rust-lang.github.io/book/ch04-00-understanding-owners...

  > "You almost never want to pass a variable by value".
So, the issue with advice like this is that it's not always true: you almost never want to pass some things by value, but Copy types, for example, you almost always want to pass by value. It's tricky!

That said, it's true that learning Rust can be tricky; we're trying to focus on improving that in the coming year. So keep it coming! This kind of feedback is very helpful, thank you.

Re:My experience rewriting Enjarify in Rust by catnaroek on Monday September 19, 2016 @10:05AM (#12531519)
Rust has both “essential verbosity” and “accidental verbosity”:

(0) Lifetimes have to be represented in the language's syntax somehow, and this can't be avoided. Lifetime elision makes this a nonissue, anyway.

(1) Rust's syntax for function declarations is horrendous. In a single place, it does two things that ought to be kept separate: state the type of the function and give names to the function's arguments. This by no means essential, e.g., Haskell does this much better.

(2) The lack of global type inference really hurts when you write functions with syntactically large argument and/or return types. AFAICT, Rust's type system is Haskell's minus HKTs plus higher-ranked lifetimes. Most Rust code doesn't use anything higher-ranked, so Rust could have much more aggressive type inference. The only impediment is the Java-like syntax for method calls: you can't infer nominal object types from how objects are used. This is again purely accidental.

Re:My experience rewriting Enjarify in Rust by masklinn on Monday September 19, 2016 @10:35AM (#12531732)
> This is again purely accidental.

WRT 2 and 3, disabling/not doing global type inference was a wilful choice, Rust's designers specifically decided not to do that and to keep type inference local.

Not splitting signatures out of function declarations comes from the same place, having removed GTI there was no point in making type signatures and function declarations independent, and it provides for uniform declaration when there's no function/method body ("abstract" trait methods, extern declarations).

Re:My experience rewriting Enjarify in Rust by catnaroek on Monday September 19, 2016 @10:37AM (#12531744)
Willful or not, it makes the language more verbose.
Re:My experience rewriting Enjarify in Rust by masklinn on Monday September 19, 2016 @10:43AM (#12531787)
I don't think it does, other syntactic decisions (e.g. C++/Java-style generic declarations, reference sigils) might, or at least make the language seem noisier, but with respect to function type declarations you'd get the exact same verbosity (in terms of tokens or characters), just split differently e.g.

    fn class(&mut self, s: &'a bstr) -> u16
versus

    class :: &mut, &'a bstr -> u16
    fn class(self, s)
all you get is repeating the function name.
Re:My experience rewriting Enjarify in Rust by catnaroek on Monday September 19, 2016 @10:43AM (#12531792)
Think about `where` clauses.
Re:My experience rewriting Enjarify in Rust by masklinn on Monday September 19, 2016 @10:46AM (#12531806)
Where clauses don't go away and their verbosity doesn't change.
Re:My experience rewriting Enjarify in Rust by catnaroek on Monday September 19, 2016 @10:47AM (#12531824)
Trait constraints in Rust are syntactically more annoying than type class constraints are in Haskell. This is in spite of the fact that, semantically, both are exactly the same thing.
Re:My experience rewriting Enjarify in Rust by valarauca1 on Monday September 19, 2016 @11:44AM (#12532299)

      This is in spite of the fact that, semantically, both are exactly the same thing.
Semantics is not practice.

Rust is a compiled language. All Types/TypeClasses have to eventually be resolved down to concrete structures/tuples that can be statically or dynamically dispatched too, and in most cases allocated to the heap/stack.

Haskell is NOT a compiled language. It compiles to a bytecode that is ran by the RTS (Run Time System) a ball of ~50,000SLOC in C/C++. Haskell TypeClasses are MAGIC managed by the run-time. They are not resolved to actual machine code. A compiled Haskell program is just the bytecode+source code+compiler+RTS bundled into an ELF. Yes some Haskell is translated into pure C, but all of it can't be. The pure C Haskell still depends on the run time heavily. Especially for managing type class dynamic dispatch.

Re:My experience rewriting Enjarify in Rust by catnaroek on Monday September 19, 2016 @01:07PM (#12532917)
> All Types/TypeClasses have to eventually be resolved down to concrete structures/tuples that can be statically or dynamically dispatched too, and in most cases allocated to the heap/stack.

My original remark was strictly about surface syntax issues (verbosity). Surface syntax is handled by the compiler's front-end. I have no idea why this should affect the compiler's backend or the runtime system. But maybe you know something about compilers that I don't.

> Haskell TypeClasses are MAGIC managed by the run-time.

No. Haskell's type classes and constraints are typically implemented as vtables (although Haskellers normally call them “dictionaries”) and extra function arguments, which are resolved at compile time. (With higher-rank types, the compiler can determine that a given vtable has to be passed around between places, though.) The runtime system has no special support for type classes - a type class instance is a record (of methods) just like any other.

Of course, Rust monomorphizes generic functions, and optimizes away the intermediate step of fetching methods from vtables whenever possible. But if you use trait objects (akin to Haskell's existentials), vtables will still be there.

Re:My experience rewriting Enjarify in Rust by tome on Monday September 19, 2016 @11:49AM (#12532329)
> Haskell is NOT a compiled language

Yes it is

> It compiles to a bytecode that is ran by the RTS

Nope, it's compiled to machine code.

(We're talking about GHC here, of course)

Re:My experience rewriting Enjarify in Rust by valarauca1 on Monday September 19, 2016 @12:32PM (#12532655)
Read the GHC's docs because they disagree with you.

https://ghc.haskell.org/trac/ghc/wiki/Commentary/Rts

https://ghc.haskell.org/trac/ghc/wiki/Commentary/Rts/Interpr...

https://ghc.haskell.org/trac/ghc/wiki/Commentary/Rts/Storage...

https://hackage.haskell.org/package/vacuum

https://research.microsoft.com/en-us/um/people/simonpj/paper...

https://research.microsoft.com/en-us/um/people/simonpj/Paper...

Haskell is a complex hybrid. TypeClasses can't always be resolved to concrete statically sized types, Haskell does RTS Magic for you when this happens. Rust throws a compiler error in this case.

Re:My experience rewriting Enjarify in Rust by jerf on Monday September 19, 2016 @01:32PM (#12533112)
Implementing an abstraction that requires indirection at runtime does not traditionally render the language "not compiled". Any definition of "not compiled" that includes the vast majority of real-world C++ is not a definition in common use.

If you want to point out that "compiled" really ought to be a continuum rather than a binary property, and that by this definition Rust is "more" compiled than Haskell, I'll be right there with you (though the pre-existing word "static" probably fits better), but we'd be creating an idiosyncratic definition not shared by very many people. (Though perhaps it should be. The binary distinction of the ~1970s really isn't that useful anymore, in a world of scripting languages, JITs, and a greater focus on the costs of indirection.)

Re:My experience rewriting Enjarify in Rust by the_why_of_y on Monday September 19, 2016 @02:14PM (#12533422)
Haskell type classes are by no means magic and the run-time does absolutely nothing with them.

Type classes are de-sugared in the compiler and turned into an extra function parameter that is a record of functions, aka "dictionary".

This is a bit similar to vtables in C++, but different because firstly, the C++ vtable is already reachable from explicit parameters/implicit "this", and secondly, C++ actually has dynamic dispatch whereas type classes are not dynamic dispatch, at least from a language point of view - they essentially correspond to "overloading" in C++.

Type classes could just as well be implemented by expanding all invocations of the function with different instances and generating monomorphic code for every combination of instances. Compilers don't do that because it would waste too much space.

Also, what exactly is a "compiled language"? The words don't mean much of significance.

Re:My experience rewriting Enjarify in Rust by catnaroek on Monday September 19, 2016 @02:35PM (#12533605)
> This is a bit similar to vtables in C++, but different because firstly, the C++ vtable is already reachable from explicit parameters/implicit "this"

Rust vtables aren't attached to all objects, but they're always attached to trait objects (whose methods are dynamically dispatched). So it's halfway between C++ and Haskell in this regard.

FWIW, I would call dictionaries “vtables”. C++ and Rust just happen to impose restrictions on how vtables can be used, so that each trait object can be bundled with its own vtable (`exists a. C a => a`), rather than potentially bundling several objects with the same vtable (`exists a. C a => T a`). But this doesn't mean that Haskell uses a fundamentally different implementation technique.

> Type classes could just as well be implemented by expanding all invocations of the function with different instances and generating monomorphic code for every combination of instances.

As long as you don't use higher-ranked types.

Re:My experience rewriting Enjarify in Rust by cousin_it on Monday September 19, 2016 @12:10PM (#12532483)
> In a systems language, it's important to know if you're passing around things by value or reference.

I'm not sure that distinction should be always in your face though. Systems or no systems, you almost never want to pass a number by reference or pass an array by value.

Re:My experience rewriting Enjarify in Rust by masklinn on Monday September 19, 2016 @12:27PM (#12532615)
> Systems or no systems, you almost never want to pass a number by reference

It does occur for generic reference-based API though (e.g. many iterators)

And you'd want to know whether you're passing a string or a vector by reference of value though, especially with affine types.

Re:My experience rewriting Enjarify in Rust by cousin_it on Monday September 19, 2016 @01:22PM (#12533036)
Why would you ever want to copy a string when passing it into a function?
Re:My experience rewriting Enjarify in Rust by dbaupp on Monday September 19, 2016 @02:00PM (#12533320)
Note that "pass by value" isn't a copy in a language with affine types like Rust.

I'm not sure passing everything by reference probably would gain much in a systems programming language in the vein of Rust: one would still need some syntactic method to distinguish between shared ("immutable") and non-shared ("mutable") references. Also, Rust-style static reasoning cares about things actually being pointers, to allow returning references safely. Lastly, being able to move things around is important too, it is a major part in Rust creating zero-overhead abstractions.

I'm not sure exactly how MLKit works, but I glanced over https://github.com/melsman/mlkit/blob/master/doc/mlkit.pdf and it seems to me that its regions are somewhat less static and that it doesn't attempt to handle concurrency. I could be wrong on both counts!

Re:My experience rewriting Enjarify in Rust by cousin_it on Tuesday September 20, 2016 @08:36AM (#12538989)
I think you could have mut visible at the language level, but otherwise have all parameter passing look and feel like Java, without any pointer noise or implicit copying.
Re:My experience rewriting Enjarify in Rust by masklinn on Tuesday September 20, 2016 @10:10AM (#12539595)
How would lifetimes work? Java & al get away with it because they don't reify lifetimes and the concepts of reference and value are moved at the type level (and they don't use affine types).
Re:My experience rewriting Enjarify in Rust by cousin_it on Tuesday September 20, 2016 @03:55PM (#12542781)
From looking at research languages with similar systems (Mezzo, LinearML...) none of them seem to have pointer noise. I might be missing something though.
Re:My experience rewriting Enjarify in Rust by masklinn on Monday September 19, 2016 @02:37PM (#12533630)
In Rust, passing a string by value is a move, and it won't copy the backing buffer at most it copies the stack structure (24 bytes) and possibly not even that (depending on the way it's used).

As to why, well because the function asks for it, either because it generically works on values, or because it consumes the string to avoid copying its internal buffer (e.g. https://doc.rust-lang.org/std/string/struct.String.html#meth...), or whatever other reason. Either way that's a completely valid use.

Re:My experience rewriting Enjarify in Rust by pjmlp on Monday September 19, 2016 @09:47AM (#12531400)
> This means &s to indicate where something is passed by a pointer,

Having had a strong background doing system programming in Wirth based languages, this was something that I never liked in C, and was quite happy that C++ followed the same path as Algol derivatives.