The Crystal Programming Language Forum

Using Rust inside a Crystal program

It will be nice to write a Crystal program and when there is a need to squeeze more performance, to use call a Rust code from the Crystal program. In addition it will just be a fun way to learn both languages. Can someone share a step by step instructions for doing it?

Google search sent me to this forum but it didn’t give me concrete steps I can follow.

Thanks!

I believe it’s the same way you call C from Crystal.

Read this: https://crystal-lang.org/reference/syntax_and_semantics/c_bindings/
And this: https://www.greyblake.com/blog/2017-08-10-exposing-rust-library-to-c/ (might be outdated, I don’t know where are the official Rust instructions for this)

Essentially, Rust can provide an API like C does. Then you link your object from file from Crystal and that’s it.

1 Like

I’m confused. How do you expect to gain more performance from Rust? Rust and Crystal should be roughly on par performance wise. Both compile to a LLVM backend, so even the optimizations are pretty similar. There are some differences in the runtimes, but I’m not aware that makes a big difference. Many benchmarks show Rust and Crystal performing pretty similar. Even if one had a slight edge over the other, it would most likely not justify the overhead for switching to the other.

This could still be a fun endevour, I just don’t think this makes sense when you’re looking for performance improvement.

1 Like

Rust has a more mature and performant multi-threading environment than Crystal currently has, which could be leveraged.

1 Like

Hello,

Perfs are similar.
I use FFI to be able to use some crates in Crystal, it allows to use Rust’s echo-system.

If it helps, here’s a little POC in bulk.

├── Cargo.lock
├── Cargo.toml
├── Makefile
├── shard.yml
├── spec
│   ├── myapp_spec.cr
│   └── spec_helper.cr
├── src
│   ├── lib.rs
│   └── myapp.cr

Makefile

ifeq ($(shell uname),Darwin)
    EXT := dylib
else
    EXT := so
endif

all: target/debug/myrust.$(EXT)
	LIBRARY_PATH=$(PWD)/target/debug:$(LIBRARY_PATH) crystal build src/myapp.cr -o myrust
	LD_LIBRARY_PATH=./target/debug ./myrust

target/debug/myrust.$(EXT): src/lib.rs Cargo.toml
	cargo build
	cd src

clean:
	rm -rf target myrust myrust.dwarf

Cargo.tml

[package]
name = "crystal-rust"
version = "0.1.0"

[lib]
name = "myrust"
crate-type = ["dylib"]

shard.yml

name: myapp
version: 0.1.0

authors:
  - Nicolas Talle

targets:
  myapp:
    main: src/myapp.cr

crystal: 0.32.1

license: MIT

lib.rs

#[no_mangle]
pub extern "C" fn string() -> *const u8 {
  "Hello World\0".as_bytes().as_ptr()
}

myapp.cr

@[Link("myrust")]
lib R
  fun string() : UInt8*
end

module Myapp
  VERSION = "0.1.0"

  msg = String.new(R.string())
  puts "from Rust #{msg}"
end

And to compile:

make

I use FFI (Rust) with the v0.34, but I have tested this POC only with Crystal v0.32.

To go further, callbacks help well. Pay attention to who has the ownership of the variables.

4 Likes

I love stuff like this because it’s like “mad scientist” type stuff :laughing: I can’t wait to see what else you come up with this.

2 Likes