News for hackers. Stuff that matters.
Re:Making Rust as Fast as Go by codeflo on Sunday May 03, 2020 @09:50AM (#23058974)
I recently did some experiments with creating small static Rust binaries, custom linking, no_std et cetera. A lot of stuff around that kind of thing is unstable or unfinished, which might be somewhat expected. But I’ve also come to the conclusion that Rust relies on libc way too much. That might be fine on Linux, where GNU’s libc is well-maintained, is a bit questionable on MacOS (as seen in this article) and is a a complete distribution nightmare on Windows (in no small part due to a series of very questionable decisions by Microsoft).

My understanding is that Go doesn’t use the libc at all and makes system calls directly, which IMO is the correct decision in a modern systems programming language that doesn’t want to be limited by 40 years of cruft.

Re:Making Rust as Fast as Go by ekidd on Sunday May 03, 2020 @10:08AM (#23059072)
As far as I know, the official system interface on Windows and several Unix systems is via the standard library, not via direct syscalls. I don't know about the MacOS. But in general, you may be required to dynamically link the standard library on many platforms.

Linux guarantees syscalls are stable. And on Linux, you have the option of telling Rust to cross-compile using a statically-linked musl-libc. (If you also need to statically link OpenSSL or a few other common libraries, I maintain https://github.com/emk/rust-musl-builder, and there's at least one similar image out there.)

Re:Making Rust as Fast as Go by majewsky on Sunday May 03, 2020 @10:15AM (#23059108)
macOS requires you to make syscalls through libSystem if you want a stable interface. Go binaries used to make direct syscalls until 1.10. Since this caused major breakage on most new macOS releases, they have since switched to using libSystem as well in 1.11:

> On macOS and iOS, the runtime now uses libSystem.dylib instead of calling the kernel directly. This should make Go binaries more compatible with future versions of macOS and iOS. The syscall package still makes direct system calls; fixing this is planned for a future release.

Source: https://golang.org/doc/go1.11

Re:Making Rust as Fast as Go by masklinn on Sunday May 03, 2020 @11:42AM (#23059665)
> I don't know about the MacOS.

MacOS literally forbids statically linking to libSystem.

Go finally had to bow down and accept that they just could not perform raw syscalls on MacOS after gettimeofday (IIRC) changed ABI multiple times during the Sierra beta.

Re:Making Rust as Fast as Go by pansa2 on Sunday May 03, 2020 @10:31AM (#23059212)
AFAIK on Windows, the hierarchy is:

C library => kernel32.dll => ntdll.dll => system calls

You don’t have to go via the C library - calling kernel32 directly is fine (I believe this is what Go does). However, it’s very rare to call ntdll or to make system calls directly.

Re:Making Rust as Fast as Go by ChrisSD on Sunday May 03, 2020 @11:26AM (#23059536)
To expand on that, on Windows it's best to use kernel32 (or WindowsApp) unless you really need the cross-platform convenience of the C lib. The exception being architecture dependent functions like memcmp, memcpy and memset which will most likely have efficient assembly implementations.

ntdll is lower level and technically unstable but core functions have been pretty stable for a long time. They could of course have breaking changes but it risks breaking things like cygwin. Microsoft tends to take compatibility seriously, although perhaps not as much as they used to.

Direct system calls are completely unstable. They can and do change a lot between releases. You'd need to use a lookup table for every build of Windows.

Re:Making Rust as Fast as Go by codeflo on Sunday May 03, 2020 @10:38AM (#23059263)
Basically yes. ntdll is an implementation detail that shouldn’t be relied upon. kernel32/user32 and friends are considered the “proper” interface to the system and have been stable for decades.
Re:Making Rust as Fast as Go by vardump on Sunday May 03, 2020 @04:57PM (#23061799)
There are some ntdll calls that are officially documented and ok to use. Of course there are also a lot of calls you shouldn't use.

When necessary, it's fine to use even undocumented ones to support Windows 7 and older. It's not like those are going to change anymore.

Re:Making Rust as Fast as Go by vardump on Sunday May 03, 2020 @10:52AM (#23059339)
> it’s very rare to call ntdll or to make system calls directly

Until you need to do something that's not possible through kernel32.dll. Sometimes I've called ntdll.dll directly to support older Windows versions.

Re:Making Rust as Fast as Go by ludamad on Sunday May 03, 2020 @11:14AM (#23059461)
I'm guessing the performance benefits are negligible anyway?
Re:Making Rust as Fast as Go by ludamad on Tuesday May 05, 2020 @11:53AM (#23081386)
Not sure why I was downvoted for curiousity
Re:Making Rust as Fast as Go by codeflo on Sunday May 03, 2020 @10:25AM (#23059177)
That’s partly true, but the stable interface on Windows is not the libc, but the kernel32/user32 functions like VirtualAlloc, CreateFileW etc. Those are stable since the NT days. The libc functions like malloc and fopen are a compatibility layer above that and unfortunately switch places every few years. Currently they are delivered by a combination of a Windows update and a redistributable package, which makes it a nightmare to ship (on pre-Windows 10 even more so).
Re:Making Rust as Fast as Go by jfkebwjsbx on Sunday May 03, 2020 @05:32PM (#23062030)
Do you really need to ship them? I thought libc (CRT?) in Windows was a given, and what used to be redistributed was only the C++ ones. Is not that the case?
Re:Making Rust as Fast as Go by codeflo on Monday May 04, 2020 @12:28AM (#23064554)
Thanks for the correction, Microsoft calls it the C Runtime/CRT. It‘s unfortunately complicated, and I’ll completely ignore static linking, which is possible, but not supported in many scenarios involving DLLs.

It used to be the case that the CRT shipped with Windows (msvcrt.dll). That file is now considered legacy/deprecated and is no longer supported by current compilers. For several years after that, you always had to redistribute the CRT (msvcrtXXX.dll), even for pure C support.

The current state of affairs is that the CRT is split into several files, some of which come with Windows update (the so-called UCRT) and some of which are compiler-specific and have to be redistributed. C++ std support requires yet more files.

This document gives an overview: https://docs.microsoft.com/en-us/cpp/c-runtime-library/crt-l...

Re:Making Rust as Fast as Go by jfkebwjsbx on Monday May 04, 2020 @06:23AM (#23066366)
Thanks a lot! It indeed seems complicated...

I understand standards evolve and that they want to modularize stuff, but in the case of C the majority programs will only use the basics of the C library.

Re:Making Rust as Fast as Go by fluffything on Sunday May 03, 2020 @10:07AM (#23059065)
> But I’ve also come to the conclusion that Rust relies on libc way too much.

How did you come to this conclusion?

People using Rust rely on libc a lot.

For example, #![no_std] means "no standard-library", but it doesn't mean "no libc, no libunwind, etc.".

So a lot of people like to advertise their crates as "#![no_std]" compatible, because they compile with #![no_std], but then the first thing the crate does is linking against libc, against liballoc, against libm, .... or even the standard library itself...

So... if you are trying to build a binary that does not depend on libc or libstd, then there is no language feature to help you there.

#[no_std] binaries are not only unstable, but also probably not what you want, since that won't prevent you from linking any library that links libc, or the standard library, etc.

If you want to avoid libc, you have to enforce that, e.g., by checking in your build system that your binary doesn't contain any libc, libstd, etc. symbols (I just use nm for this). #![no_std] helps a bit here, but making sure you don't link any library that violates this is up to you.

Re:Making Rust as Fast as Go by geofft on Sunday May 03, 2020 @10:19AM (#23059133)
To me, a #![no_std] library is useful for contexts where there isn't a platform libc (embedded systems, kernels, etc.) and you can't assume things like the existence of stdin or processes. On those systems, there may still be a dynamic allocator, because that's a super common feature in all but the most spartan environments. For those use cases, a #![no_std] library that links liballoc (and documents that it needs it) is totally okay, and often better than a more-limited library that only does static allocation - or conversely implementing all of libstd with runtime panics for most of it, just because your library needs to pass a Vec around. It's a balance.

The only case I think I've seen where a #![no_std] library ends up pulling in libc is if you haven't added a custom allocator and your platform's default allocator uses libc (and so you could switch to a libc-free allocator if you want). Are there other cases?

Re:Making Rust as Fast as Go by codeflo on Sunday May 03, 2020 @10:49AM (#23059317)
There are lots of tiny practical annoyances you notice, starting with having to vet dependencies and transitive dependency upgrades extremely carefully. A major one is that dev-dependencies are very broken in a no_std environment. It’s obviously useful to have the std in your build.rs script, but unfortunately, Cargo merges dependencies and devDependencies in a way that’s simply incorrect.

The good news is that every problem I found had an actively discussed GitHub issue and the community is active, so there will be progress.

Re:Making Rust as Fast as Go by geofft on Sunday May 03, 2020 @12:55PM (#23060202)
> unfortunately, Cargo merges dependencies and devDependencies in a way that’s simply incorrect.

Yeah, I've run into that, but IIRC this got solved super recently/is being solved. Probably https://github.com/rust-lang/cargo/pull/7820 ?

Re:Making Rust as Fast as Go by fluffything on Sunday May 03, 2020 @12:11PM (#23059885)
> To me, a #![no_std] library is useful for contexts where there isn't a platform libc (embedded systems, kernels, etc.) and you can't assume things like the existence of stdin or processes.

"Could be useful for" (FTFY).

Unfortunately, for the reasons mentioned above, most #![no_std] libraries aren't useful for that, because they link libc and other libraries.

Most people don't do this intentionally. The compiler doesn't complain about:

    #![no_std]
    extern "C" { fn malloc(...) -> ...; }
so when somebody does it accidentally, everything works and they get no feedback.

When you then go and build a #![no_std] binary, and run it in a platform when libc is not available, only then you get an error. And at that point, good luck figuring out which of your 200 dependencies has the error.

In particular, if you are running `#[test]`s, doing that links standard, so if your program implicitly depends on libc somewhere, tests won't reveal that, because while testing, libc will be linked.

Re:Making Rust as Fast as Go by geofft on Sunday May 03, 2020 @12:52PM (#23060179)
No, most #![no_std] libraries do not link libc in the way you describe (having a direct FFI dependency on malloc) - they link liballoc, which has a pluggable allocator interface. On some platforms and some configurations (including most normal userspace platforms), the default allocator used by liballoc uses malloc.

It's actually hard to go out of your way and call malloc directly, because FFI calls are unsafe. It's a lot easier to use Box/Vec/String/etc., all of which are defined in liballoc and use the pluggable allocator interface.

I know this because I've successfully used #![no_std] libraries in places where libc doesn't exist and no function named malloc exists, and they do work. If you're having a linker issue it's almost certainly because you haven't changed the default allocator - if you have an example of this I'd be happy to take a look at debugging it.

Re:Making Rust as Fast as Go by fluffything on Sunday May 03, 2020 @04:25PM (#23061599)
> and they do work.

Maybe you are just using different no_std libraries that I am using, but pretty much all of the no_std libraries that I use have `libc` as a direct dependency.

Not only for malloc, many of them just needs to write something to stdout or a file, generate random numbers, ..., and that's hard to do without libc. Why they advertise themselves as no_std escapes my comprehension.

Re:Making Rust as Fast as Go by geofft on Sunday May 03, 2020 @04:55PM (#23061784)
Huh, the libc crate itself claims #![no_std] support. I agree that's confusing and counterintuitive... I see what it means in terms of semantics but I don't understand why that's useful.
Re:Making Rust as Fast as Go by fluffything on Sunday May 03, 2020 @04:59PM (#23061807)
The libc crate does correctly claim #![no_std] support, because #![no_std] means "Does not require the Rust standard library", and libc does not require it.

The two main issues I see with #![no_std] are:

* its a flag for "doesn't link the standard library" but the standard library is often too high-level for bare metal apps that want to be in precise control about everything that gets linked

* it isn't a contract of any kind, so you can't rely on it for anything. In fact, this code is correct and works as intended:

    #![no_std]
    extern crate std;
This is problematic, because many #![no_std] libraries end up linking libstd "by accident", even though that's probably not their intent.

So I agree with you that #![no_std] isn't a silver bullet. I think it is still useful in that it lets you avoid linking the standard library by default, which is necessary condition for embedded development. It is not a sufficient condition, in that in practice you actually want to forbid the standard library and other libraries from being linked, and not just "don't link them by default, but do so by accident".

Re:Making Rust as Fast as Go by andoriyu on Monday May 04, 2020 @06:38PM (#23074203)
`#![no_std]` really means that sysroot isn't available for your platform and you gotta roll your own.

Project I'm working on right now is `#[no_std]`, but my own sysroot covers 90% of real `std`. Which is why I even export it as `std`, so I can use pretty much all crates I would usually use. Because if your platform has an allocator, doesn't matter where it came from, then you can add `liballoc`. `libstd` = `libcore` + `liballoc` + locks + allocator + threads + a few other things that usually depend liballoc. Which means that you have nearly entire libstd available to you if your target platform has an allocator.

Most `#[no_std]` crates that depend on allocator clearly state that they depend on `liballoc` and that you gotta provide one. What I'm trying to say `#[no_std]` doesn't mean embed, it just means sysroot isnt' available for target.

Re:Making Rust as Fast as Go by codeflo on Sunday May 03, 2020 @10:14AM (#23059102)
That’s true. But going the no_std route is very hard (the ecosystem isn’t big, and relying cargo and crates.io you’re almost guaranteed to link in the std or liballoc by accident at some point). Even when using the std intentionally, I really wouldn’t have expected that basic std functions like println require the libc.
Re:Making Rust as Fast as Go by geofft on Sunday May 03, 2020 @10:25AM (#23059171)
I would not necessarily expect it but I appreciate it in a "systems" language where "systems" is defined as compatible with the existing traditional systems software on a machine. For example, it's nice if the stdbuf(1) command works on Rust binaries on glibc systems, and it's nice if a Rust binary calling a C library that writes with printf (or vice versa) don't maintain two separate output buffers.

To me, Go is the systems programming language for a world untethered by existing platform compatibility (and so, for instance, writing Go libraries to be called from C is awkward, calling C libraries from Go incurs overhead, Go has its own concurrency model, etc.) and Rust is the systems programming language for use cases where you'd otherwise want to use C (really "the platform's native systems language," but that's C on all the major platforms) but you want a better language. I appreciate that they both exist and target these different use cases.

Re:Making Rust as Fast as Go by BuckRogers on Sunday May 03, 2020 @12:55PM (#23060200)
Is Go widely used compared to the others as a systems language at this point?

Over the years I’ve gathered that it’s more of a competitor for C# & Java rather than Rust & C(++).

Re:Making Rust as Fast as Go by geofft on Sunday May 03, 2020 @01:03PM (#23060264)
Depends what you mean by "systems language" (many common definitions turn out to be equivalent to "drop-in replacement for the platform language," which makes the argument circular), but the choice of Go as an implementation language for Docker and Kubernetes puts it pretty firmly in the "systems language" space IMO. Container management requires more systems-level functionality than C# and Java are really geared towards (although you certainly could use them, perhaps with a tablespoon of FFI).
Re:Making Rust as Fast as Go by andoriyu on Monday May 04, 2020 @06:42PM (#23074231)
Uhm, docker is just a wrapper around linux's kernel features, plus image format. Image format being just bunch of tarballs that needs to be mounted by _something_ layer by layer.

You can write it any language that runs on OS with linux's kernel. There are similar things written and damn bash. Does that make bash a system language?

Re:Making Rust as Fast as Go by apta on Sunday May 03, 2020 @02:30PM (#23060904)
The original version of Docker was written in Python, and the original version of Kubernetes was written in Java, so your argument doesn't hold.
Re:Making Rust as Fast as Go by geofft on Sunday May 03, 2020 @04:11PM (#23061504)
Would this be the part of my argument that doesn't mention Python (which I personally consider a systems language) at all? Or the part of my argument where I say, "you certainly could use them"?
Re:Making Rust as Fast as Go by apta on Monday May 04, 2020 @11:12AM (#23068985)
It just means that golang isn't anything special when it comes to writing tools like these, and is in the same ballpark as python and Java (it's somewhere in between the two of them, better than python but worse than Java), and you'd have to use the same escape hatches in golang as them to implement those tools.
Re:Making Rust as Fast as Go by GlitchMr on Sunday May 03, 2020 @04:51PM (#23061759)
Well, writing to standard output requires interacting with an operating system, so it's not surprising that it requires `std`. You cannot write to standard output without knowing what operating system you are compiling for.

Worth noting however that `writeln` only needs `core`, and as long you can provide an implementation of writing to standard output (pretty much create a struct and implement `core::fmt::Write` trait for it), creating `println` macro is trivial.

Re:Making Rust as Fast as Go by steveklabnik on Sunday May 03, 2020 @05:08PM (#23061859)
Re:Making Rust as Fast as Go by cesarb on Sunday May 03, 2020 @08:26PM (#23063106)
> [...] relies on libc way too much. That might be fine on Linux [...]

> My understanding is that Go doesn’t use the libc at all and makes system calls directly

Actually, the only system on which it's fine to "not use the libc at all and make system calls directly" is Linux. On MacOS, Windows, and most non-Linux Unix-like systems, you must to go through the libc or its equivalent (which on Windows is kernel32.dll and/or ntdll.dll), since the system call interface is unstable (the libc or its equivalent is distributed together with the kernel, so it's updated whenever the system call interface changes).

AFAIK, Go tried for a while to use system calls directly on MacOS; after being broken several times by operating system updates, they gave up and now go through the shared libraries like everyone else. They still insist on using direct system calls on Linux, where it works mostly fine (except for things like the DNS resolver, in which AFAIK they try to guess whether they can do directly network DNS requests, or have to go through libc; and that one time in which Go's small stacks conflicted with a misoptimized kernel VDSO using too much stack).

Re:Making Rust as Fast as Go by steveklabnik on Sunday May 03, 2020 @10:13AM (#23059097)
The problem with this is that not every system makes their system call ABI stable. You have two choices here: use the interface that is stable (which is libc), or track changes and fix things up when they break.
Re:Making Rust as Fast as Go by codeflo on Sunday May 03, 2020 @10:19AM (#23059130)
The only stable interface on Windows are the documented functions from kernel32.dll, user32.dll etc. Libc is a compatibility layer above that, that Microsoft invents a new incompatible distribution mechanism for every 3-5 years. It’s pure DLL hell unfortunately.

Edit: Not even the DLL name of Microsoft’s libc is stable (msvcrt140.dll etc.), leading to all kinds of wild goose chases when trying to run old binaries.

Re:Making Rust as Fast as Go by steveklabnik on Sunday May 03, 2020 @10:39AM (#23059266)
Yes, I was being imprecise wrt Windows, thanks :)
Re:Making Rust as Fast as Go by codeflo on Sunday May 03, 2020 @10:52AM (#23059334)
Yeah, I saw your username too late or I would have known that you know that. ;) But it’s a common misunderstanding among many Unix programmers, so I feel it was good to clarify.
Re:Making Rust as Fast as Go by steveklabnik on Sunday May 03, 2020 @10:57AM (#23059368)
Nah I think it's helpful! Comments are read by a lot more folks than just the person you're replying to. I knew it wasn't exactly libc but I didn't know the full hierarchy involved.
Re:Making Rust as Fast as Go by rhinoceraptor on Sunday May 03, 2020 @12:19PM (#23059943)
Go’s pathological NIH syndrome does come with downsides. For example, there was an infamous memory corruption bug in the way they called into vDSO.
Re:Making Rust as Fast as Go by adev_ on Sunday May 03, 2020 @12:31PM (#23060033)
> Go’s pathological NIH syndrome does come with downsides.

Yes. And you can add the inability to use the glibc's nss modules under Linux.

Making it unable to use sssd properly and authenticate a posix user on a machine with LDAP authentication.

Getting completely independent from OS sys lib has consequences

Re:Making Rust as Fast as Go by alecthomas on Sunday May 03, 2020 @07:08PM (#23062644)
This is not accurate. [1]

When compiled on Linux for Linux, Go will use libc and natively call NSS.

When cross-compiling to Linux from another system, Go requires (mostly) CGO to be disabled and a subset of NSS will be implemented in Go. Native NSS modules will not work.

[1] https://github.com/golang/go/issues/24083

Re:Making Rust as Fast as Go by weff_ on Sunday May 03, 2020 @10:22AM (#23059150)
I think Go tries to reduce its dependence on libc but, by default, it will still link to it.

For instance, this code:

  package main
  import "net"
  func main(){
    net.Dial("tcp", "golang.org:80")
  }
When compiled with go build main.go does link:

  linux-vdso.so.1 (0x00007ffe3d7f0000) 
  libpthread.so.0 => /lib/x86_64-linux-gnu/libpthread.so.0 (0x00007fc7ac05a000)
  libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007fc7abc69000)
  /lib64/ld-linux-x86-64.so.2 (0x00007fc7ac279000)   
There are of course compiler options to truly statically compile.
Re:Making Rust as Fast as Go by fortran77 on Sunday May 03, 2020 @10:51AM (#23059327)
This is simply false. Windows APIs are stable. It's one of the best platforms for back compatibility. The kernel32/user32 API has been stable for 20 years. Rust has some work to do.
Re:Making Rust as Fast as Go by codeflo on Sunday May 03, 2020 @10:58AM (#23059378)
That’s exactly my point: The problem is that Rust doesn’t use the stable kernel32/user32 functions (VirtualAlloc etc.), but the libc ones, which don’t have a stable location on Windows. Without looking it up: Which DLL do you have to redistribute this week to get “malloc”?
Re:Making Rust as Fast as Go by fortran77 on Sunday May 03, 2020 @06:08PM (#23062244)
I just tell Visual Studio to create an installer, I select the set of target platforms, and it just works. See, for example: https://docs.microsoft.com/en-us/cpp/ide/walkthrough-deployi...