Hacker News new | past | comments | ask | show | jobs | submit login
D 2.069.0 released, compiler automatically ported from C++ to D (dlang.org)
142 points by jacques_chirac on Nov 4, 2015 | hide | past | favorite | 125 comments



Daniel Murphy is the man behind getting the sources converted from C++ to D. He wrote a program called "magicport" to do the bulk of it, with some manual tweaking. The back end is still in C++, showing that you can mix D and C++ code :-)


How is the compiler bootstrapped now (i.e. compiled from source without a working D compiler)? Via the gcc D implementation?


If my information is still actual, the last C++ based implementations will be used for the time being for such purposes.


Using the previous version write on C++. Like all compilers of other languages did when change to be write on his own language. Or do you think that the first version of C compiler was write on C ?


Funny anecdote for compiler researchers.

Niklaus Wirth did write many of his compilers in the original language (kind of).

He would write down on paper the code, using the bootstrap version 0 code style, as it was supposed to be.

Then he would manually translate that code into Assembly.

So when the compiler for the basic language was working, he could use the same code again, without additional efforts and relying on third party languages.

Specially important back in the day where each computer system had its own systems programming language or dialect of an existing one.


Computer languages were a lot simpler in those days.


That's true. Yet, Wirth's model is worth considering today. In Lilith system, they co-designed a high-level assembler (P-code-like), the Modula-2 language, the compiler, and critical OS regions. Built whole system on that. Key choice, learned from P-code, was to make assembler, high-level language, and compiler all consistent and simple where possible. Allowed easy composition and conversion.

Work on LISP/Scheme, Julia, REBOL, and so on show that even complex constructs can be built with macros, etc on simple ones which can be done Wirth-style as in Lilith/Oberon, my style [1], or by hand like pjmlp said. Actually, the Scheme stuff went down to synthesizing hardware from interpreters written in Scheme and another person did that with a Oberon-based HDL. So, it can go much further. :)

[1] https://news.ycombinator.com/item?id=10182752


Are you sure? I remember reading something about PL/I. :)


Maybe PL/S, IBM's "secret weapon," (haha) would be better if we're talking C++ replacements. I did like how the language let you describe how exactly the compiler should handle the individual function. I can see that have payoff in OS and security-critical software.

https://en.wikipedia.org/wiki/IBM_PL/S


Interesting, I wasn't aware of it.


Walter has been around a long time. He knows the game. Walter, have you ever used PL/I?


Nope. But Pascal was so simple that a listing for a working subset compiler for it (written in BASIC) was published in BYTE magazine back in the 70s.

But I (and many other compiler devs) thought back in the early 80's that Ada was so complex it was unimplementable. Today that thought seems charmingly naive.


I have spent quite some time in D forums....

EDIT: typo, time was missing


That's kinda how lisp came to life, wasn't it? It was supposed to be a theoretical exercise, but someone took the "lisp in lisp" code and manually compiled it to assembly.


That's not really the only possible way. You could write an (arbitrarily slow & simple) interpreter in e.g. C to compile the compiler with itself, or translate to C, or use something like a p-code machine as an intermediate step (https://en.wikipedia.org/wiki/P-code_machine) with an assembler-written interpreter. The problem with using older versions is that in principle, you'd have to keep maintaining/porting them for newer systems/architectures.


Now all we need is some E to celebrate!



Hm this sounds just like the port of the Go compiler from C to Go. In that case it was C and Go, rather than C++ and D.


D is a really amazing language that manages to correct the mistakes of c++ but still retain its good parts. it's a shame that it's not more popular.


To me, while D fixes many warts of C++, D is still too similar to C++ to justify the change. It still has implicit numeric cast, exception unsafety and strange behaviors.[1]

In addition, the D authors are generally opposed to disciplined approaches, e.g. type classes, region-based memory management, which are being added to C++. Especially regarding the former, while "design by introspection" may have its merits (Andrei Alexandrescu's presentation on allocator is worth watching[2]), I think many still prefer explicit interfaces over implicit ones, so I don't see D take off in the near future, at least until the wanted features are added to the language.

[1] http://forum.dlang.org/thread/htmkdnmlqyvkidkrsmri@forum.dla...

[2] https://www.youtube.com/watch?v=mCrVYYlFTrA


> It still has implicit numeric cast,

Implicit numeric casts that lose bits are not allowed anymore.

> exception unsafety

??


> Implicit numeric casts that lose bits are not allowed anymore.

That's good to hear, but I want implicit conversion to be forbidden unconditionally (even int * float -> float). I've been bitten by even widening conversions, so I just want to let them go away. It might be great if such an option could be applied to module level.

> ??

Sorry if my understandings or my words were wrong, but I got the impression that D also suffers from destructors that throw. (double-throw) Is this not the case in D? If so, could you elaborate how it achieves that?

I'm personally rather fond of purging exceptions from the language level completely, like Go did(Of course panics are technically exceptions, but their usage is culturally discouraged). Of course, this is just my personal opinion, but I would be happy if D had a story on this problem.


Concerning exceptions, I personally hate how Go implemented their error handling. This Quora answer explains it perfectly: https://www.quora.com/Do-you-feel-that-golang-is-ugly

Not that I'm a huge fan of exceptions either, but Go is definitely not a reference when it comes to error handling. My personal favorite, by far, is still CL's condition system.

Still, I've never had any issues with exceptions in D. Even exceptions thrown in destructors will raise a core.exception.FinalizeError with all the relevant information attached to it.

I've worked on large (1M+ LoCs) C++ codebases with exceptions disabled. Threading error codes all over the place, everywhere, is not pretty. Someone always forgets a check somewhere and you then spend 2 weeks figuring out why it crashes. It also pollutes the instruction stream with branches all over the place.

D also has assertions, unittests, preconditions, postconditions and invariants built into the language.

Also, unless there's actually an exception raised, frame handlers are faster than return code checks (its implemented as 3 MOV instructions per try block instead of test+jumps on every function call).


I rather like the implicit conversions. Especially in D where they only happen where its safe.

There's the 'to' function to perform checked conversions and the standard cast operator to perform unchecked conversions.

int foo = 256; ubyte bar = foo.to!ubyte; // throws ConvException ubyte baz = cast(ubyte) foo; // overflows as expected

If I want more type-safety than this, I'll create simple wrappers:

struct Seconds { int value; alias value this; }

That way I can still pass seconds everywhere an int is expected, but I can now declare arguments to only accept Seconds and not ints.


std.typecons.Typedef does something similar: http://dlang.org/phobos/std_typecons.html#.Typedef


> I want implicit conversion to be forbidden unconditionally

Not going to happen in a language with 11 integer types.

> (double-throw)

Exceptions are chained together.


Walter, is there any chance that we will see the source code to Digital Mars C++ release at some point? Or is it encumbered in ways that would make such a release impossible?


You can buy a copy of the source code from Digital Mars if you like. It's a bit old fashioned, though. At some point I'll probably just make it free, though it'll still have the Symantec license on it.


I often wonder if it were released today as "new" if it would of gained plenty of more hype. One thing I think that Go has that D is lacking is the standard library. With Go you can write a web server, a mail server, and plenty of things right away out of the box. Where in D and other lovely languages you either need to get a package manager, or make your own. Outside of this small detail I think D is amazing at what it's done, and I hope to work on more projects in D in the future. I only wish schools used D in more classes. I guess the last thing it's lacking is a serious IDE. I've seen and tried a couple, but at the end of the day I end up using a text editor with a hoard of plugins.


C++ and D aren't the kind of languages that get hype because they're the kind of languages that see serious use right away. If you're deep in the trenches writing C++ and/or D code, you don't have the time or the need to generate hype. You're getting actual work done. Things are different for languages like Ruby or Go. These were used more as flights of fancy for certain people, many of whom were not getting real work done. So they had time to write lengthy blog articles, make YouTube videos, host conferences, and write really weird and absurd tutorials. These are the kinds of things that generate hype. Hype is a product of everything but code; actual code is the anti-hype.


There's the dub package manager for D. http://code.dlang.org/

I don't think I'd want web and mail servers straight into the standard library out of the box. Those are way too specific. For example, there's already vibe.d for web servers and its doing an excellent job. They even have compile-time HTML templates!

As for IDEs, on Windows I've recently used VisualD and it did a good job. It's not yet as complete as the C# integration of Visual Studio (especially when taking ReSharper into account), but it definitely feels like an IDE. It already has improved hugely over when I last used it last year.


Not sure what I do wrong every time I try D again, I wanted to see the new backtraces worked but I still get:

  /bin/bash: line 1: 82501 Segmentation fault: 11  ./main
while I try to call a method on a null variable, which is not that friendly to newcomers.

Sample code:

  import std.stdio;
  
  void main()
  {
  	Greetings g = null;
  	g.hola();
  }


I had the same problem with D. As an example of what I feel stack traces should look like, compare it to the stack traces that Nim gives.

  ~/temp_code » ./greetings
  Traceback (most recent call last)
  greetings.nim(9)        greetings
  greetings.nim(6)        hola
  SIGSEGV: Illegal storage access. (Attempt to read from nil?)
For the code:

  type
    Greetings = ref object
      name: string

  proc hola(g: Greetings) =
    echo g.name

  var g: Greetings
  g.hola()


That code won't compile because Greetings is undefined. Hence, you may be running some other program which seg faults.


ohh, yeah, I omitted the definition of the class because I thought it'd be obvious, here it is:

  $ cat main.d
  import std.stdio;

  class Greetings {
	void hello() {
		writeln("hello");
	}
  }

  void main()
  {
	Greetings g = null;
	g.hello();
  }
  $ dmd -g main.d && ./main
  Segmentation fault: 11
  $ uname -a
  Darwin Johan-Ride-Mac.local 15.0.0 Darwin Kernel Version 15.0.0: Sat Sep 19 15:53:46 PDT 2015; root:xnu-3247.10.11~1/RELEASE_X86_64 x86_64
  $ dmd --version
  DMD64 D Compiler v2.069.0
  Copyright (c) 1999-2015 by Digital Mars written by Walter Bright
(EDIT: added osx and dmd versions)


Replace: Greetings g = null; with: auto g = new Greetings();


I'm intentionally making it null to see how the new backtraces work.

I know it's because I'm trying to call a method on "null", but If I'm working on a huge code base, how do I debug this kind of issues if I don't have stack trace with line and error number of the error?

I'd like to get at least the stack trace and the error number with the line of the source code that caused the problem, like in golang:

  $ cat main.go
  package main
  
  type Greetings struct {
  }
  
  func (g Greetings) hello() {

  }

  func main() {
	g := &Greetings{} // heap instance
	g = nil           // intentionally shooting myself in the foot like I did in D
	g.hello()
  }
  $ go run main.go
  panic: runtime error: invalid memory address or nil pointer dereference
  [signal 0xb code=0x1 addr=0x0 pc=0x2025]

  goroutine 1 [running]:
  main.main()
	/Users/ride/Documents/main.go:13 +0x15

  goroutine 2 [runnable]:
  runtime.forcegchelper()
	/opt/boxen/homebrew/Cellar/go/1.4.2/libexec/src/runtime/proc.go:90
  runtime.goexit()
	/opt/boxen/homebrew/Cellar/go/1.4.2/libexec/src/runtime/asm_amd64.s:2232 +0x1
  exit status 2


null deference in D doesn't throw exceptions by default on Unix so you won't see the backtrace there.

If you are working in a big codebase and have this come up, you can enable core dumps and run it in a debugger to get far more information than just a line number (and line numbers are there too at least if compiled in debug mode).


does this mean there is not a way to get stack trace on OSX?


I have not tried it myself but there seem to be ways to get a stack trace on Linux.

http://vibed.org/docs#handling-segmentation-faults


I don't know about an automatic one, but running the debugger would still work there too. Or at least it should.


Try valgrind


Did you compile with debug info (-g I think)? It needs to read the DWARF info to get the line numbers.


I think so:

  $ dmd -g main.d && ./main
  Segmentation fault: 11
is there anything else I can try?

I'm OSX El Capitan 10.11.1


On Unix segfaults by default don't generate stack traces. This makes it work on linux, don't know about OSX (put it in your main module):

  import etc.linux.memoryerror;
  
  shared static this() {
   	static if (is(typeof(registerMemoryErrorHandler)))
   		registerMemoryErrorHandler();
  }


The handler works in a real ubuntu VM but not in docker (the image is probably missing glibc https://github.com/D-Programming-Language/druntime/blob/mast... so that explains "static if" in your snippet)

to be honest, it's better than nothing but still kinda difficult to work with:

  etc.linux.memoryerror.NullPointerError@src/etc/linux/memoryerror.d(325)
  ----------------
  ??:? void etc.linux.memoryerror.sigsegvUserspaceProcess(void*) [0x439045]
  ??:? void etc.linux.memoryerror.sigsegvDataHandler() [0x438f92]
  ??:? _Dmain [0x435c17]
  ??:? _D2rt6dmain211_d_run_mainUiPPaPUAAaZiZ6runAllMFZ9__lambda1MFZv [0x4376da]
  ??:? void rt.dmain2._d_run_main(int, char**, extern (C) int function(char[][])*).tryExec(scope void delegate()) [0x437630]
  ??:? void rt.dmain2._d_run_main(int, char**, extern (C) int function(char[][])*).runAll() [0x437696]
  ??:? void rt.dmain2._d_run_main(int, char**, extern (C) int function(char[][])*).tryExec(scope void delegate()) [0x437630]
  ??:? _d_run_main [0x43758d]
  ??:? main [0x436075]
  ??:? __libc_start_main [0xb0fa1ec4]


Are you using the new version and compiling with -g there? Those addresses should be translateable into line numbers.

You can also do it manually btw with `addr2line -e your_executable 0x435c17` and the sort.


Thanks, I tried again and it seems to be working:

  $ dmd -g main.d && ./main
  etc.linux.memoryerror.NullPointerError@src/etc/linux/memoryerror.d(325)
  ----------------
  ??:? void etc.linux.memoryerror.sigsegvUserspaceProcess(void*) [0x423321]
  ??:? void etc.linux.memoryerror.sigsegvDataHandler() [0x42326e]
  main.d:12 _Dmain [0x4204e5]
  ??:? _D2rt6dmain211_d_run_mainUiPPaPUAAaZiZ6runAllMFZ9__lambda1MFZv [0x421b8a]
  ??:? void rt.dmain2._d_run_main(int, char**, extern (C) int function(char[][])*).tryExec(scope void delegate()) [0x421ae0]
  ??:? void rt.dmain2._d_run_main(int, char**, extern (C) int function(char[][])*).runAll() [0x421b46]
  ??:? void rt.dmain2._d_run_main(int, char**, extern (C) int function(char[][])*).tryExec(scope void delegate()) [0x421ae0]
  ??:? _d_run_main [0x421a3d]
  ??:? main [0x420597]
  ??:? __libc_start_main [0xdb825ec4]
this is linux, I use mac as development machine, what can I do for mac? is there a error handler for mac too?


1) Where is Greetings defined ?

2) Spanish ?



This is the first time I found out that DMD isn't self-hosting; I always assumed it was.


Me too. Figure something good enough to replace C++ immediately and better to develop with would make for a better compiler. Not to mention all the advantages of writing a compiler in the language in question.

Only exception I promote is writing compilers in ML (esp Ocaml) because it's so good at doing that correctly. A concentration of compiler writers on such a great tool can only lead to an ecosystem whose quality C or C++ compilers will have trouble matching. Rewriting and testing the LLVM system at the least would be a good thing.


I'm wondering, is there an advantage of using D over Rust?


Some things D has that Rust doesn't: compiler-checked function purity annotations, higher-kinded types, variadic functions/generics, types parameterised by numbers, compile-time function evaluation, mixins, a fast compiler (the reference DMD compiler), powerful and convenient compile-time reflection (I think technically Rust can do anything D can at compile time, but it requires writing a syntax extension to do so). For better or worse, D also has classical OO inheritance, which Rust lacks.


I would say this is fairly accurate, yeah. We got rid of purity in Rust, we didn't find it useful. HKT, variadic generics, and type-level integers are all things we want to do in the future.


The ship's probably sailed on this, but is there any chance of Rust getting a module system like OCaml's (or Haskell's upcoming Backpack) in future? Or is there a way to provide module signatures with the current module system?


Never say never, but I'm not aware of anyone who's even working on thinking about suggesting it. That'd be step one, creating an RFC which thinks through the design.


There's this: https://internals.rust-lang.org/t/traits-ml-modules/272, but it didn't seem to get anywhere.


Right, that was just a few posts, a long time ago, and never really came close to being a serious proposal.


Rust and D are both great. The difference is that Rust has a hugely powerful hype train, which happens to be operating at 1,000% capacity somehow since the beginning of Rust.


Rust inherited the enthusiasm of some very prominent and outspoken members of the Ruby community. These ex-Rubyists honed the craft of projecting excitement with Ruby and RoR, and brought these skills and talents with them when they moved to the Rust camp. The D camp hasn't really had anyone like that join them.


I have to agree on this. I was surprised to see that a majority of the Rust community actually comes from some web-development domains. Especially when comparing Ruby, a highly flexible dynamic language, to Rust a language full of constraints (for its own valid reasons).

Note that the a hype train is a two-edged sword, it will both bring a strong community and might also induce a cliff with the other communities (praising a single god is never a good thing).


An alternative interpretation might be that rust has particular appeal to those that primarily use dynamic languages. It's probably both, but I will say that as someone that's busy getting stuff done in Perl[1] every day, rust looks appealing, and a lot of that has to do with guarantees. If I'm going to give up the convenience of Perl for a lower level language[2], I need to be sold. Rust's promise of provably correct memory management sounds really good in that situation.

1: I've used lower level languages, but for nothing large, and I haven't started a project in C, C++ or even Java in over a decade, and was never very enamored.


As someone who fits this mould, for me, it's not exactly that. Yes, in my professional life, people know me for my Rails work, but I learned C at a very young age, and while my systems-level stuff was a bit out of practice, for me, Rust is a _return_ to the kind of stuff I used to do earlier in life.

Oh, and I'm not sure "majority" is really true. Yeah, maybe some of us have disproportionate impact, but a very large number of people in Rust-land really know their systems stuff. I am humbled to work alongside such talented people.


As someone who's been following the hype, but not really jumped on yet, the difference (whether real or imagined) seems to be that rust is trying to do something new, and D looks to be (and I've seen it aggressively marketed as) C++ with some better choices and cool features. Personally, I'm not really interested in C++, but I am interested in getting for familiar with a systems language, so rust interests me.


From what I understand, the only new thing Rust is contributing is managed lifetimes. Everything else is, like in D, just borrowed from other languages and put together.

That said, I've mostly heard that lifetimes are still too young to be worth the trouble. So the one new thing Rust does bring to the table isn't really ready yet.


I think of it like this: If I'm going to manually be managing memory, I might as well get some real gain for that effort. Provably correct memory management through an ownership system sounds like it might be worth the effort, especially if it might also help in situations with threading. I've had reason to use threads quite a bit in the past.


> That said, I've mostly heard that lifetimes are still too young to be worth the trouble. So the one new thing Rust does bring to the table isn't really ready yet.

Not in my experience. I wrote tons of safe Rust every day and I really enjoy not having to worry about difficult-to-debug crashes, undefined behavior, and security problems.


> just borrowed from other languages and put together.

Most of these things are new to the systems space, though. Things like algebraic data types, etc. Also usable affine types.

> lifetimes are still too young to be worth the trouble.

I've heard pretty much the opposite. Firstly, nobody who uses Rust calls the feature "lifetimes" (it's usually called borrowing or borrow checking). That's the name of the syntax, and while the syntax is new and somewhat confusing it doesn't pop up that much thanks to elision.

Borrowing has a learning curve, yes. But really, it's an equivalent set of principles to what you would do in C++ to keep your pointers safe. The difference is that you can write small C++ codebases without worrying about "C++ borrowing", whereas you need to think about these things off the bat in Rust.


> it's an equivalent set of principles to what you would do in C++ to keep your pointers safe.

The things you can prove statically with the borrow checker are a subset of the things that wont trigger UB in C++.


That doesn't contradict what I said.

My point was: If you're working on a large C++ codebase you will have to reason roughly about who owns what and where your pointery data is coming from. IME it amounts to (roughly) the same thing as Rust's borrowck rules, just that Rust forces you to think about this a priori, in a clean framework with clear rules, instead of wibbly wobbly, pointy-wointy, ... stuff.

The things you feel safe to do in Rust are often a superset of the things you feel safe to do in C++. Most large codebases often use lots of shared_ptr or equivalent, whereas in Rust Rc and RefCell are broken out only when necessary (when the compiler tells us there's no way to do it the borrow way).

So while C++ might let you write a larger variety of non-UB patterns in principle, Rust will let you write more non-UB patterns in practice. You feel safe playing fast and loose with the references, and dancing near the line of safe behavior since the compiler ensures you never cross it. I have a post illustrating an example of this here: http://manishearth.github.io/blog/2015/05/03/where-rust-real...

And, of course, if you really want to express one of those "things C++ lets you do" wrt pointers, you can always break out `unsafe`.


Your comment has the structure of a well reasoned argument, but it's predicated on falsehoods.

Most large C++ projects do not unnecessarily use reference counting.

Most C++ programmers do not fear using references or pointers because of invalidation.

The Rust compiler does not tell you when it's impossible to satisfy your problem using borrows. In fact it often has false positives requiring borrow gymnastics because it is too primitive.

And your last line is a complete reversal of your previous attitude: Suddenly you are focusing on theory while ignoring that in practice you can not usually break out `unsafe` to solve your borrow problems. In fact it's Rust, not C++, that is more likely to reach for more heavyweight abstractions than necessary because it's not practical to litter unsafe throughout your codebase.


> Most C++ programmers do not fear using references or pointers because of invalidation.

Compared to Rust? I doubt that. Like I said, Rust lets you dance near the line.

This is from experience in various large C++ codebases. I'm not saying people use refcounting a lot, I'm saying it gets used more than Rust.

YMMV though, so it does boil down to a matter of different experiences here. We'll probably have to agree to disagree.

> In fact it often has false positives requiring borrow gymnastics because it is too primitive.

Not really. Aside from non-lexical borrows and a couple other nice-to-have things (but not necessary), the borrow checker is pretty precise for what it tries to prove.

One might argue that the guarantees Rust tries to maintain (one writer or multiple readers for a piece of data) are too primitive. I don't think that's true. Doing a context-sensitive/flow-sensitive analysis might lead to more patterns being allowed but it's hard to scope guarantees when the analysis is interprocedural -- stopping at function boundaries makes sense to me.

> The Rust compiler does not tell you when it's impossible to satisfy your problem using borrows.

It sort of does. If you try to introduce borrows and listen to the suggestions the compiler gives you, and eventually end up nowhere, it's probably not possible to do it that way. It's not perfect, but it's good enough. And it's immune to further changes -- you don't need to design your pointer usage so that it's future-proof; design it however you want, and if a future refactoring introduces a possible use-after-free, fix the compile error.

> And your last line is a complete reversal of your previous attitude: Suddenly you are focusing on theory while ignoring that in practice you can not usually break out `unsafe` to solve your borrow problems.

No, it's not, I'm just pointing out the equivalence. My point was that "The things you can prove statically with the borrow checker are a subset of the things that wont trigger UB in C++." is irrelevant for two reasons -- (a) in practice IMO the things Rust makes you feel safe to do is a superset of what C++ lets you feel safe to do, and (b) if we're going to talk about "all possible things C++ theoretically allows you to do", then you should include unsafe -- you were comparing "safe Rust" with "all C++", which is unfair here, since the entities to be compared for what you're theoretically allowed to do are "all Rust" and "all C++". I focused on a different flaw in your argument, so of course the focus changed. I'm not saying you should use unsafe a lot, I'm saying "what C++ lets you do is equivalent to using unsafe a bit more often in Rust". I don't endorse it, but if you consider "C++ lets you do so many things Rust doesn't" to be a plus point of C++ (I don't), then you should at least be comparing the right things and allowing yourself to use unsafe in Rust too.

> more heavyweight abstractions than necessary

Examples?


> Compared to Rust? I doubt that. Like I said, Rust lets you dance near the line.

Your problem is that you are trying to imagine what happens in the real world rather than just looking at it.

> This is from experience in various large C++ codebases. I'm not saying people use refcounting a lot, I'm saying it gets used more than Rust.

I count 56 imports of Arc and 37 of Rc in servo, would you wager the equivalent parts of chromium or firefox use more ref-counting?

> Not really. Aside from non-lexical borrows and a couple other nice-to-have things (but not necessary), the borrow checker is pretty precise for what it tries to prove.

The problem is that the borrow checker does not look at the function as a whole. You are a slave to the borrow checker, you can only consider other things when it has been satisfied, even if its trivially safe from a whole-function view. Some simple things aren't even expressible at all without non-lexical borrows, no matter what gymnastics you try.

> It sort of does. If you try to introduce borrows and listen to the suggestions the compiler gives you, and eventually end up nowhere, it's probably not possible to do it that way.

That sounds like a terrible workflow that I don't think Rust programmers use in the real world. I think they usually use shared ownership because they know they have shared ownership, not because they gave up on the borrow checker. Any instance of actually resorting to reference counting because of the inability to make the borrow checker happy actually counts against Rust, not for it.

> in practice IMO the things Rust makes you feel safe to do is a superset of what C++ lets you feel safe to do

People routinely do safe things in C++ that would be impossible to express in safe Rust code. You need to let go if this untruth.

> if we're going to talk about "all possible things C++ theoretically allows you to do", then you should include unsafe -- you were comparing "safe Rust" with "all C++", which is unfair here, since the entities to be compared for what you're theoretically allowed to do are "all Rust" and "all C++".

I focused on safe Rust because your argument was that safe Rust is just as powerful as C++, just proved safe statically. This is far from the truth and you refuse to accept the correction. Most people in the Rust community would probably say they accept the loss in power because they prefer absolute safety. This is a very defensive position. It's impressive just how little power Rust gives up in comparison to languages like C#/Java. But to pretend that you actually have more power in Rust due to psychological effects is just zealotry and doesn't reflect how people use C++ in the real world.

> Examples?

Any abstraction that uses dynamic checking that would be considered a bug to actually trigger. Some examples include borrowing a RefCell, random access of iterators, sequential access of certain kinds of iterator adaptors, etc.


> your argument was that safe Rust is just as powerful as C++, just proved safe statically. This is far from the truth and you refuse to accept the correction.

No, it wasn't; sorry if you thought it was. My argument was that the cognitive overhead of satisfying the borrow checker isn't much different from the cognitive overhead in writing safe C++ code. Perhaps I shouldn't have used the word "equivalent", but that was my original point.

Then you said "The things you can prove statically with the borrow checker are a subset of the things that wont trigger UB in C++.", and I made the theory/in practice distinction, but at no point did I say that was wrong, I just said it wasn't relevant to my point.

Just to be clear, this is the set of things I believe:

- Rust lifetimes/borrowchk have little to no additional cognitive overhead as compared to writing safe code in C++

- Rust lets you approach problems without having to worry so much about safety, which lets you play more fast and loose with references (avoiding unnecessary refcounting, etc)

I agree with you that:

- C++ lets you do more patterns safely than safe Rust lets you compile; and some of these patterns are used often.

- Rust needs to work on improving on things like nonlexical borrows.

I disagree that the above two are major problems or come up often in practice whilst programming Rust. It could be that we've had different experiences whilst programming Rust, however.

> I count 56 imports of Arc and 37 of Rc in servo, would you wager the equivalent parts of chromium or firefox use more ref-counting?

Sure. Firefox has two garbage collectors. Not one. Two. (One of them is Spidermonkey's, which is expected, since Javascript is GCd. The other is for the DOM)

Firefox also has many different types of FooRefCounted base classes, which seem to be used all over the place.

Almost all the Arc is in the highly-parallel layout.

Note that "imports Rc" just means that it deals with an Rc'd value, not necessarily introduces a new Rc'd value. (On the other hand in C++ often you use base classes for Rc, which means that the derived class is Rcd everywhere)

For example, in all of the Servo DOM code, the Page is Rc'd (I forgot the reason, but we don't thrash the refcount too much there), and JS callbacks are Rcd (because of a sticky interaction with the JS runtime, to be expected of something like that), and nothing else. That itself accounts for 20 imports of Rc, even though it's just two kinds of things being Rcd. Arcs are used in code interfacing with the network or layout stack (I think, I haven't looked closely)

> People routinely do safe things in C++ that would be impossible to express in safe Rust code. You need to let go if this untruth.

I agree with this. That's not what I'm saying. I'm not refuting the existence of safe things C++ lets you do that Rust doesn't. I'm just saying that Rust lets you feel safer

> I don't think Rust programmers use in the real world.

Sure, that's not how we program. Because for the vast majority of cases since lifetime annotations exist it's pretty easy (for a Rust programmer) to figure out how to borrow things safely. But there are cases where you're unsure; and it takes very little time to try something out and see what happens.

> Any instance of actually resorting to reference counting because of the inability to make the borrow checker happy actually counts against Rust, not for it.

I don't see refcounting being used to "make the borrow checker happy"; I see it being used in cases where its necessary.

> Some simple things aren't even expressible at all without non-lexical borrows, no matter what gymnastics you try.

I disagree, in my experience it's very rare to hit a situation where you need nonlexical borrows.

> Some examples include borrowing a RefCell

Ah, right. I don't consider refcell too "heavyweight" (and often it can be replaced with the zero-cost Cell), but yes, it does get broken out more than necessary sometimes. I agree with you on that.


D's new things have been more accidental than by design... and kinda subtle too. Like the compile time reflection. I'm sure someone else has done it somewhere before, but D makes it really easy.


  > lifetimes are still too young
While some stuff is new, the core of the idea goes back to Cyclone in 2001.


I think I'm climbing aboard the hype train. The thing that made a difference for me was the lack of GC. I knew I could have an easier time selling that to my team than GC. As soon as they hear 'GC' they will probably start to think of unacceptable latencies and lump D in with Java, Python (et al).

That said, D's compatibility with C++ is a big plus.


On the nontechnical side I would add that since D is a lot older, keeping up with changes is a whole lot less challenging. Its stable enough that one can put it near the money without worrying too much about breaking changes.


Since 1.0 last May, we have a strong commitment to backwards compatibility as well. Stability should not be an issue any longer.


My impression is that things are settling down now, but I've encountered quite a few rust code bases that required "latest rust" (latest > 1.0) - and even "nightly".

Not to say that rust-the-language hasn't stabilized, but I think the point about D being (way) more mature still stands?

(Note, it's a good thing that people are writing code to test/work with/exercise latest rust/nightly etc -- but just because I can be fairly sure code I write today will compile tomorrow (which is good!) -- that doesn't mean "all of rust land" is stable).

Please don't take this of critique of all the hard work going into rust -- the only way this wouldn't be a thing was if rust core was a dead project.

[ed: To add to that - it might well be that all of the projects I've seen lately where I've had to wake up multirust[1] and do a little dance before my beautiful random code from random person on the Internet have been willing to compile -- have all been bad samples of rust code - but they're still part of the landscape of rust code one encounters in the wild.

[1] https://github.com/brson/multirust (eats a bit of space (~200MB per version of the compiler/toolchain) - but in my very limited testing works nicely!)]


> quite a few rust code bases that required "latest rust" (latest > 1.0) - and even "nightly".

In my experience most of these either use old features which have a stable counterpart, or use compile-time codegen (there are stable libraries to do this now, though they aren't as ergonomic as plugging into the compiler directly)


Yeah, I mean, I don't take this as a critique, it's the same for basically every language. When you build a project with a new version, you might take advantage of the new features. Such is life. :)


GP is actually the second post in a week I've seen where someone mentions tracking rust changes, but rust has been 1.0 for a while now. Have there actually any backwards incompatible changes since 1.0, or are people really complaining about a problem that's been fixed for close to six months and even then was specific to beta versions?


There haven't been backwards-incompatible changes in practice, no.

That is to say: there have been technically backwards-incompatible changes, but every compiler and language, including those of C++ and Java, make technically backwards-incompatible changes in point releases (e.g. defining new functions in the libraries and modifying undefined behavior). Rust doesn't make changes that violate the stability guarantee. It goes further than that, in fact—any change that is not breaking according to the public guidelines but is thought possibly breaking in practice is tested against crates.io and feedback is solicited from those who have any private crates that might be affected to make sure it doesn't break them either.

(Unfortunately this policy is often misunderstood—for example, it's been claimed that Rust will make breaking changes as long as they don't break anything on crates.io, which is very much not an accurate description of it.)


Due to its erratic development and the much-delayed release of Rust 1.0, I fear that many people have associated Rust with frequent breaking changes, even if that is no longer the case. It's much like the "Java is slow" fallacy; it may have been true in Java's very early days, but it hasn't been the case for many, many years. Yet the misconception still persists, even to this day. This is the sort of taint that a language will find very hard to shake. The "Rust suffers from breaking changes" misconception is very ingrained in the minds of many programmers now.


> higher-kinded types

Well, not really. Higher-kinded type parameters are something that requires a kind (typeclass) system in the first place, which D intentionally doesn't have (as D's authors are opposed in principle).


kinds and typeclasses are rather different, are they not?


Yeah, I shouldn't have made it read like I was equating the two. What HKT means is that typeclasses, like Monad, can take parameters of higher kinds. But if you don't have typeclasses in the first place (like D doesn't), then you don't have HKT.


Well this is completely wrong. HKT's have nothing to do with type classes.


I'm referring to what people want when they say they want "higher-kinded types" in Rust. That is: the ability to have typeclasses with higher-kinded type parameters.

I'm well aware that the formal definition of an HKT is just a type with a higher kind, and that's irrelevant to this discussion.


What certain Rust users want to do with HKT can already be done with D (maybe they're LKT's, loosely-kinded types -- you heard it here first!) or, for example, quite explicitly, C++, without type classes, so it's quite relevant.


No, it's not relevant. When people say they want HKT, what they mean is that they want to create typeclasses that abstract over types of a higher kind and only those types, with a type system that can make those guarantees.

Saying C++ has HKT because it doesn't have typeclasses is like saying Python has all of Haskell's type system features because it doesn't have static types. There's a sort of vacuous sense in which it's true, but it's not a particularly meaningful or interesting thing to say.


We have proof in this very thread that people don't use the term HKT that way. That makes sense, because that's not what the words "higher kinded types" mean.

> Saying C++ has HKT because it doesn't have typeclasses

I didn't say anything like that at all. C++ has HKT, and it doesn't have type classes.


If you consider `template<template ...>` to be HKT then Rust has this too. Rust macros can be used like C++ templates (minus autoinstantiation) and they can take other macros as parameters. It's not as nice as C++, but ... workable.

Patrick is talking about HKTs which can be typechecked at the source, similar to the rest of Rust generics, when he talks about "they want to create typeclasses that abstract over types of a higher kind and only those types". C++ templates get typechecked at use-time, not instantiation-time, like Rust macros. On the other hand, Rust forces you to constrain types in generics (a la C++ "concepts") so that it typechecks at the source (and then the contract for types being passed into the function is clear in the signature). We don't have higher kinded bounds, though.

HKT, after all, is a comment on bounds. What sort of things are you allowed to substitute for this generic or template parameter?

What seems to be happening here is a difference of opinion on what constitutes a "bound", in the C++ world a "structural" bound (i.e. type vs number vs template) is all you have. In the Rust world this isn't considered a bound, since the bound isn't type-safe. In the Rust world, Haskell world, and most other worlds where the word "kind" is used, a bound is a "type" (typeclass) bound and is type safe -- you can only do operations on the parameter which the bounds let you. Many Rust/Haskell people would consider C++ templates to be a particularly awesome macro system (but not a generics system); since they're structural-bound (like Rust macros); not type-safe.

Really a matter of terminology though.


Exactly. If C++/D have HKT, then so does Rust via its macro system.


C++ and D do it differently, I don't know why you're lumping them together. (Well, actually, I lied, I do know why.)


I guess D feels more like a better C++ and Rust like a better C, but I may be under the wrong impression.


Rust is more like the child of C and a functional language (OCaml).


Still think that *ML syntax would fit Rust better.


I disagree. D is an improved C that does a lot of the same things as C++, but in order for it to be a better C++, it would need to start with C++ as a foundation and build on it. D has more of a scripting language feel to me.


> D has more of a scripting language feel to me.

I'm curious about this, although I don't think this is the first time I've heard D described as having a script-y feel to it. It sounds along the same lines as people describing Go as almost a scripting language, or comparing it to them. Neither Go nor D seem especially dynamic, both have compilers for reference implementations, both look more like C than Python. (To my eyes at least; I've never tried either one. Thus this question.)

Is it primarily the speed of the compilers? Both DMD and g6/g7/g8 boast very fast compiles, and Go uses somewhat script-y tools... but #!/usr/local/bin/tcc -run hasn't changed C's reputation. Fast compiling and memory safety, maybe?


People often say this because D has many features that allow it to be almost as flexible as the various scripting languages while still being compiled and statically typed. I don't bother with bash/cmd nowadays as D can easily fill that role, with the bonus of my small little scripts having all the power and scalability that D offers.

Also check out https://github.com/Abscissa/scriptlike


How would you consider Rust's generics as being part of a better C rather than a better C++?


C translates almost 1:1 to Rust, but when converting C++ to Rust you'll run into impedance mismatch between OO hierarchies and traits, and generics being narrower in functionality than templates.

In Rust you still can do "clever" things with generics to make them feel like C++, but the rest of the language is still closer to C: errors returned rather than thrown, no inheritance (but the "flat" OO and enums map well to what OO-like C programs do with "handles"), no constructors, etc.


Really? Now I have to go learn Rust.


> errors returned rather than thrown

WHATT??? did they do the same mistake as Go?


It depends on what mistake you mean.

Having errors on return has merits. Particularly it make error handling more deterministic. What it tends to do though, in languages like C and Go, is be very verbose.

Rust largely mitigates that through judicious usage of some of its higher levels features. Specifically its try! macro wrap up the common case of passing errors up the call stack quite neatly. With that and the pervasive RAII, Rust does a pretty good job of making error management quite unobtrusive.

It also has quite a nice way of converting errors types using traits that, in my opinion at least, handles typed error propagation better than most exception mechanisms I am familiar with.

From a personal pov, I like the determinism and I like the type conversion. I probably prefer D's scope mechanism to RAII in general as I like thinks to be as explicit as possibly but without being too verbose. But I think Rust seems to have hit quite a nice balance.


No. There's a few things that combine to make it different, but what it really boils down to are enums. Rust functions that can fail return a type, Result: http://doc.rust-lang.org/std/result/enum.Result.html

There's a number of things that make using this ergonomic: you can use the try! macro to convert it to a success value, returning an error value up the stack. You can use any of the combination methods on that page to chain various possibly-failing functions together into something that looks nice. Some people even use the word "monadic." We don't have if checks everywhere.

For more than you probably want to know on error handling in Rust, this guide just hit stable: https://doc.rust-lang.org/book/error-handling.html


I think it's more about Rust's approach to OO. In Rust, traits are usually used as interfaces for generics instead of dynamic dispatch. The generated code/performance is more like what you would get if you implemented them explicitly for each appropriate type in C, built around structs. You could do this with C++ templates, but it would be a huge pain in that language due to lack of template type safety.

This is a quite important part of Rust (IMHO) because it gives the language a performance profile closer to C than C++ in many instances.


Hmm, I see your point, but I've found that even in template-heavy code the compiler can produce code that's pretty optimal. Are you saying that with Rust it is easier to fall into the pit of success, as it were -- requiring less reasoning about how a compiler might convert the [templated|generic] code optimally?


My point was not that Rust creates better template code, but that a lot of things one uses dynamic dispatch for in C++ use templates instead. There are plenty of times when one knows the full derived type of a class in C++, but one throws away that information virtual calls are used anyway.

I think this is because Rust makes it far easier to use generics; they're type-safe and don't need to be pushed to header files. Also, the coding culture around Rust pushes one to use enums for dynamic dispatch instead of "trait objects" (Rust's equivalent of VFTs).


> but one throws away the information ...

With the mythical smart enough compiler with god like devirtualizer it would be less of an issue but gratuitous use of virtuals annoys me no end. CRTP isn't that hard for the simple use cases but you are beholden to the god of compiler error messages when things go wrong. One great thing that clang has done (among others) is make g++ get their shit together. Contrary to the popular sentiment I personally find the current crop of g++ error messages more helpful.


Another big part of the barrier here is the nature of compiled binaries. When you define a public generic function in Rust, the compiler ensures that it keeps enough information in the resulting binary so that the generic can be instantiated. Unless you wanted to massively balloon your headers and compile times, achieving the same with C++ is simply impossible across executables and libraries, and very hard even across object files.


While OOP is perfectly possible in Rust, IMHO it is a lot more like doing OOP in C than in C++ (despite nice features like traits and generics).

Rust is marketed as a "general-purpose, multi-paradigm, compiled programming language" and D "originated as a re-engineering of C++", so may be I'm biased by my interpretation of the goals from both language designers.


I wish the ObjC was supported for Windows x86 (>= 7) for linking with Gnustep.

In any case, congratulations.

I wonder if the C++ to D translator could be used in other codebases, like Fltk for example.


You could use it as a starting point, but it was never intended to be a generic tool. For example, the C++ code was changed in some places to make the job for the translator easier.


D is what you get if C married Python and the resulting baby was adopted and raised by Lisp and C#.


How's the memory-safety model in D without a GC, last time I looked at it, it seemed to me that C++ 11/14 has the better model here and now with the C++ core guidelines, it may be even better.


Currently it still relies on the GC.

However you can write gc free code regions by marking them as @nogc and the compiler will make sure you only call code that is @nogc compatible.

Additionally, there is some ongoing discussion how to improve the language to depend less on the GC.

The C++ core guidelines are great, and me being a C++ fan even with its warts, welcome them and all the work the C++ community is doing.

However them being opt-in, relying on static analysis and the quality of the code I usually see at companies, which tends to be C with Classes from developers that don't even know what CppCon is, I am not sure how the adoption will be like.


Has the core guidelines Checker tool been released yet? For those that haven't studied the guideline intensely, I'd assume it's necessary to do any kind of real world comparison.


Could you elaborate? What about the C++ 11/14 model improves memory safety?


I imagine he/she means the newly introduced C++ Core Guidelines with the _prt<>() and _view() classes, and the adoption of Rust like lifetime analysis for static analysers.


[deleted]


Those sorts of transpilers are usually tailored to a specific project, so it would probably not work on bitcoin-core. Of course if bitcoin-core was committed to moving to D they could fork the transpiler and tailor it to their own code base.


I have no idea what the GP post said, but even if it did work, see patio11's comments (on the internet) on the need to reproduce bitcoin-core's behavior exactly.




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

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

Search: