I'm in the process of writing a tutorial, because I couldn't find a simple example anywhere, of communicating between Elixir and Rust via a Port.
I can get Rustler to work, but that is a NIF, not a Port.
I'm missing something fundamental in my code. I'm not sure if I'm missing something basic in stdio or if it's something else, but I've tried a lot of different things.
I can get port communication to work with a very basic program in Rust:
use std::env;
fn main() {
println!("hello world!");
}
I can get this to get pulled into my iex -S mix by running this port:
defmodule PortExample do
def test() do
port = Port.open({:spawn_executable, "_build/dev/rustler_crates/portexample/debug/portexample"}, [:binary])
Port.info(port)
port
end
Here's what the iex for that looks like:
Interactive Elixir (1.4.2) - press Ctrl+C to exit (type h() ENTER for help)
iex(1)> PortExample.test()
#Port<0.9420>
iex(2)> flush()
{#Port<0.9420>, {:data, "hello world!\n"}}
:ok
iex(3)>
I can do the same using a porcelain library call:
alias Porcelain.Result
def porcelain() do
result = Porcelain.exec("_build/dev/rustler_crates/portexample/debug/portexample",["hello", "world"])
IO.inspect result.out
end
corresponding iex:
iex(3)> PortExample.porcelain()
"hello world!\n"
"hello world!\n"
iex(4)>
However, as soon as I start using a Rust library with some form of input/output, things start falling over.
For example, Rust code:
use std::io::{self, Write, Read};
fn main() {
let mut input = String::new();
let mut output = String::new();
for i in 0..2 {
match io::stdin().read_line(&mut input) {
Ok(n) => {
println!("input: {}", input.trim());
io::stdout().flush();
}
Err(error) => println!("error: {}", error),
}
}
}
I can get it to compile and run in the command line:
hello
input: hello
world
input: hello
world
However, when I call it from an Elixir port:
iex(12)> port = PortExample.test()
#Port<0.8779>
iex(13)> Port.command(port, "hello")
true
iex(14)> Port.command(port, "world")
true
iex(15)> Port.command(port, "!")
true
iex(16)> Port.command(port, "more")
true
iex(17)> flush()
:ok
iex(18)> Port.info(port)
[name: '_build/dev/rustler_crates/portexample/debug/portexample',
links: [#PID<0.271.0>], id: 4697, connected: #PID<0.271.0>, input: 0,
output: 15, os_pid: 21523]
I get no data from it at all! However, the Port.info(port)
call shows that its received 15 bytes. It just hasn't posted returned anything at all to the port. I've been trying to read other code and I thought I was doing things similar enough that it should work, but it doesn't.
I thought: maybe the buffer isn't flushed? so I flush the buffer in Rust. I thought: maybe the loop is hanging, so I limited it to only a few passes. When I try to run this same code through the porcelain call, it hangs.