Skip to content

IPC — Inter-Process Communication

rayforce ships a TCP client for talking to a running RayforceDB server over RayforceDB's native IPC protocol. This lets you run queries on a remote (or local) RayforceDB instance and exchange Values with it.

Assume a live runtime

use rayforce::{Runtime, TcpClient, Value};
let _rt = Runtime::new()?;

Running a server

The client connects to a RayforceDB server process. Start one by running the rayforce binary in server mode with -p PORT:

rayforce -p 5000

The server now listens for IPC connections on port 5000.

Embedded server is planned

An embedded TcpServer you can run from within Rust is planned but not yet available in the bindings. For now, run the standalone rayforce binary as shown above.

Connecting

Create a client with TcpClient::connect(host, port, user, password). Pass empty strings for user / password when the server requires no authentication. The call returns Result<TcpClient>, so a failed connection is an error you handle.

use rayforce::{Runtime, TcpClient};
let _rt = Runtime::new()?;

let client = TcpClient::connect("127.0.0.1", 5000, "", "")?;
# Ok::<(), rayforce::RayError>(())

The connection is closed on drop — when the TcpClient goes out of scope the socket is released. You can also close it explicitly with client.close().

Executing queries

execute(query) sends a Rayfall source string to the server, runs it there, and returns the result as a Value.

use rayforce::{Runtime, TcpClient};
let _rt = Runtime::new()?;

let client = TcpClient::connect("127.0.0.1", 5000, "", "")?;

// A scalar result.
let sum = client.execute("(+ 1 2)")?;
assert_eq!(sum.as_i64()?, 3);

// A vector result, read back zero-copy.
let v = client.execute("(til 5)")?;
assert_eq!(v.as_slice::<i64>()?, &[0, 1, 2, 3, 4]);
# Ok::<(), rayforce::RayError>(())

Server-side errors come back as a Result::Err:

use rayforce::{Runtime, TcpClient};
let _rt = Runtime::new()?;
let client = TcpClient::connect("127.0.0.1", 5000, "", "")?;

assert!(client.execute("(undefined_symbol_xyz)").is_err());
# Ok::<(), rayforce::RayError>(())

Sending values

To send a Value (rather than a source string), use send. It transmits the value to the server, waits for the response, and returns it as a Value:

use rayforce::{Runtime, TcpClient, Value};
let _rt = Runtime::new()?;
let client = TcpClient::connect("127.0.0.1", 5000, "", "")?;

let payload = Value::vec(&[1i64, 2, 3]);
let reply = client.send(&payload)?;
# Ok::<(), rayforce::RayError>(())

For fire-and-forget messages where you do not need a reply, send_async sends a value and returns immediately:

use rayforce::{Runtime, TcpClient, Value};
let _rt = Runtime::new()?;
let client = TcpClient::connect("127.0.0.1", 5000, "", "")?;

client.send_async(&Value::sym("ping"))?;   // returns () on success
# Ok::<(), rayforce::RayError>(())

A full example

use rayforce::{Runtime, TcpClient};

fn main() -> rayforce::Result<()> {
    let _rt = Runtime::new()?;

    // Connect to a server started with: rayforce -p 5000
    let client = TcpClient::connect("127.0.0.1", 5000, "", "")?;

    // Run a query remotely.
    let result = client.execute("(+ 1 2)")?;
    println!("server says: {}", result.as_i64()?);

    // The connection closes automatically when `client` is dropped.
    Ok(())
}

See also

  • Serialization — the same wire format used by IPC, available directly via Value::serialize / Value::deserialize.