
Rustgo: Calling Rust from Go with near-zero overhead - FiloSottile
https://blog.filippo.io/rustgo/
======
masklinn
> But to be clear, rustgo is not a real thing that you should use in
> production. For example, I suspect I should be saving g before the jump, the
> stack size is completely arbitrary, and shrinking the trampoline frame like
> that will probably confuse the hell out of debuggers. Also, a panic in Rust
> might get weird.

> To make it a real thing I'd start by calling morestack manually from a
> NOSPLIT assembly function to ensure we have enough goroutine stack space
> (instead of rolling back rsp) with a size obtained maybe from static
> analysis of the Rust function (instead of, well, made up).

> It could all be analyzed, generated and built by some "rustgo" tool, instead
> of hardcoded in Makefiles and assembly files.

Maybe define a Go target to teach Rust about the Go calling conventions? You
may also want to use "xargo", which is specially built for stripping or
customising "std" and to work with targets without binary stdlib support.

~~~
valarauca1
This is more complex then just a target. Rust has to communicate to the Go
Runtime Environment.

Two main points:

-Go uses a very small stack for GoRoutines to make them dirt cheap. When you exceed this stack, Go maps in more stack for you transparently. Rust generated ASM is running on the Go Stack, but it is expecting when it exceeds its stack to explode, like a C program. As that should be the OS Stack. Larger problem then you think, Rust likes to put a ton of stuff on the stack. This is one of the nice things about Rust is putting _a ton_ of data on the stack is cheap, and makes ownership simpler.

-Go's system calls, and concurrency primitives are cooperative with its runtime. When make they communicate the routine can yield to the runtime. Targeted Rust code would _also_ have to make these calls, as well as 3rd party crates.

Again none of this is impossible, linker directives and FFI magic could import
these functions/symbols. But this would also require Go have a stabilized
runtime environment for other languages to link against. Currently just
stating Go has a runtime is controversial, so I expect this won't happen soon.

~~~
weberc2
> -Go uses a very small stack for GoRoutines to make them dirt cheap. When you
> exceed this stack, Go maps in more stack for you transparently. Rust
> generated ASM is running on the Go Stack, but it is expecting when it
> exceeds its stack to explode, like a C program. As that should be the OS
> Stack. Larger problem then you think, Rust likes to put a ton of stuff on
> the stack. This is one of the nice things about Rust is putting _a ton_ of
> data on the stack is cheap, and makes ownership simpler.

This point confuses me; if Rust expects to run on a limited stack, why would
it expect to put a ton of data on the stack?

> Currently just stating Go has a runtime is controversial

I've never heard any controversy... The Go community certainly call it a
runtime, and there's even a "runtime" package. Do folks from VM languages get
grumpy because Go's runtime is statically linked?

~~~
masklinn
> This point confuses me; if Rust expects to run on a limited stack, why would
> it expect to put a ton of data on the stack?

Rust runs on a C stack, while it's not infinite[0] it's a whole other ballpark
than a Go stack since it's non-growable (Rust used to use growable stacks
before 1.0): the default C stack size is in the megabyte range (8MB virtual on
most unices[1]), in Go the initial stack is 2 _k_ B (since 1.4, 8k before).

[0] you can set the size to "unlimited", systems will vary in their behaviour
there, on my OSX it sets the stack size to 64MB, Linux apparently allows
actual unlimited stack sizes but I've no idea how it actually works to provide
that

[1] I _think_ libpthread defines its own stack size circa 2MB so 8MB would be
the stack of your main thread and 2MB for sub-threads, but I'm not actually
sure

------
danenania
I haven't tried Rust yet, but I've been building libraries in Ruby, Node, and
Python that call into a shared Go core, and my experience has been that the
best approach is to simply compile static executable binaries for every
platform, then call out to them in each language via stdin/stdout. I tried
cgo, .so files, and the like, bit this was a lot more trouble and had issues
on both Windows and alpine-flavored linuxes.

Is there some issue with this approach that I'm missing? Is the additional
process overhead really enough that it's worth bending over backwards to avoid
it?

~~~
warent
That sounds like it could be a nightmare for error handling! How do you work
with that? And aren't a great many performance benefits lost when you restrict
Go to stdout?

I would think it would be better overall to just create a local http server in
Go and use that instead. Or sockets if you're feeling up to it.

~~~
danenania
The executable has really simple output--it either works and outputs json, or
doesn't and outputs nothing--so there's no difficulty with error handling. I
guess I can understand wanting tighter integration for more complex scenarios
though... an http server is an interesting idea, but could you run into issues
with ports being restricted on production servers?

I'm not seeing any performance issues with stdout, but I'm also not writing
much data.

~~~
tokenizerrr
> but could you run into issues with ports being restricted on production
> servers

Sorry, what? Just make the port configurable?

~~~
danenania
It was a question (note the question mark)... I don't see the need for snark.

Anyway, for my purposes, this wouldn't work, since the executable is embedded
in libraries that are meant to run anywhere without any configuration. But
yeah I could see that being fine under other circumstances I guess.

~~~
tokenizerrr
No snark. Just puzzled. You can still just make it configurable. Or just pick
a random port and communicate it with your child process.

------
uluyol
Building the rust code into a syso file might make the (user) build process
easier here. This is used for the race detector (based on tsan) and there's an
example of building and using one in the dev.boringcrypto branch. This would
require a package author to create syso files for all GOOS, GOARCH
combinations they care about. Although GOOS might not matter depending on
whether any syscalls can be made from rust.

[https://go-review.googlesource.com/c/55472](https://go-
review.googlesource.com/c/55472)

------
drej
This is crazy. I love it.

------
ericfrederich
Language interop in 2017 is still pretty dismal.

C is still the common denominator, you'd think it'd be easy, but it's hard.
Years ago when LLVM was showing promise and Google was going to get Python
running on top of it I was hopeful.

I guess nowadays it's a better design to run separate processes and have your
languages communicate out of process (pipes, http, etc) rather than in-
process.

------
dm319

      Go strives to find defaults that are good for its core use
      cases, and only accepts features that are fast enough to be 
      enabled by default, in a constant and successful fight
      against knobs
    

Made me chuckle.

~~~
dilap
I loved that line too.

"A constant and successful fight against knobs" really gets at a lot of what
makes Go (often) a joy to use.

------
kmicklas
If you're already writing Rust, why would you even bother writing Go?

~~~
Thaxll
The same reason why Go is used to write many applications where Rust is not.

\- Fast compilation

\- Multi platform

\- Great tooling

\- Good std lib

\- Easy learning curve

\- Fast enough for most scenarios

\- Good concurrency model

~~~
saghm
> Great tooling

With the caveat that I'm still fairly new to Go, I'm not sure that I'd call
this a complete win for Go over Rust. With regards to editing tools, yes, Go's
has the edge; completion, formatting, and function lookup are all still better
than anything Rust has to offer, although rustfmt has come leaps and bounds
from where it used to be, and RLS looks very promising. With regards to what
I'll call "infrastructure" tooling (building, packaging, testing, etc.), Rust
absolutely wins, hands down. Cargo is miles ahead of anything I've heard about
in the Go world, which currently has multiple commonly used tools for
dependency management (none of which are nearly as good as Cargo), and I've
already encountered Makefiles and custom shell scripts for common building
tasks that would be a breeze with Cargo in the few weeks I've been building
Go. That's not to say that there might not be equally good options out there
for Go, but if they exist, they don't seem to be universally used, which
mitigates their usefulness.

More on a personal opinion level, I also utterly despise the way GOPATH works.
I generally try to group the projects I work on by category rather than by
language (i.e. ~/code/projects for side projects, ~/code/forks for open source
repos I contribute to but don't maintain, ~/code/scratch for simple projects
in each language I use for if I want to try out a package or something, etc.),
which GOPATH is completely incompatible with, as I'd need to add each of them
to my GOPATH and then put all my Go projects of each type into a "src"
directory in each of them and then either move all my non-Go projects there or
have a bunch of Go-specific directories littered around in each of them. If I
don't put my Go projects in a ":src" directory inside one of the paths on my
GOPATH, then I can't use gorename to change the name of a variable or
function, which is frankly ridiculous. This isn't to say that my way of
organizing my projects is the "best" way, but I feel like enforcing a
directory structure _outside of the project that the tool is being used in_ is
borderline hostile to the user.

~~~
sellweek
Thankfully, [https://github.com/golang/dep](https://github.com/golang/dep) is
almost stable now, should be merged into tip by the 1.10 release and after
that, a reform of GOPATH is planned so a lot of this nastiness should be gone.

~~~
saghm
Nice! I've looked into dep, and it was clear to me that it would eventually be
the standard; I just hope that everyone switches to it once it's released as
part of the official tools.

------
smegel
What has this got to do with Rust? Nothing. He could have called any C library
and it would have been exactly the same. I am pretty sure there is a crypto
library or two written in C.

He is just writing a more direct manual version of CGo in assembly that
bypasses a lot of what CGo does, to be much faster.

> Before anyone tries to compare this to cgo

The only meaningful message in this blog is it possible to write a faster CGo,
that's it. Comparing it to CGo is the only useful possible outcome, but...

> But to be clear, rustgo is not a real thing that you should use in
> production. For example, I suspect I should be saving g before the jump, the
> stack size is completely arbitrary, and shrinking the trampoline frame like
> that will probably confuse the hell out of debuggers. Also, a panic in Rust
> might get weird.

So when you actually fix all those things you might be back where CGo was at
the beginning.

This guy comes across as a classic "but i wanna be cool" hacker who discovers
that when you bypass all the normal protections in a library and make some
kind of direct custom call, things can be faster.

I guess so what?

~~~
legendiriz68
Only comment which actually realized what the article was about
and...downvoted. Thanks HN-retards.

------
artursapek
Is rust actually faster than go? I had no idea.

~~~
random023987
Rust has an explicit goal of zero-cost abstractions, while Go will allow more
expensive abstractions in the service of simplicity.

~~~
ori_b
For the kind of numerical code here, it's not going to be significantly
faster.

~~~
bascule
curve25519-dalek is 3X faster than the equivalent Go code (i.e. in the stdlib,
authored by agl).

This is for a few reasons:

Rust has support for 128-bit integers (i.e. u128) which allows for faster
arithmetic when operating on what are effectively multi-u128 bignums in an
elliptic curve library.

LLVM is generally more sophisticated about optimizations. It wasn't until
recently that go had an SSA-based compiler, so its optimizer isn't nearly as
sophisticated as LLVM's, which has been developed for many years now.

~~~
pjmlp
There is also gccgo, taking advantage of GCC's backend.

There is some ongoing work into an LLVM based compiler for Go, and LLVM now
has Go as official supported bindings.

