콘텐츠로 이동

가이드 목록 | 이전: Go

Rust 바인딩 가이드 (zlink)

이 장의 계약 소유 문서Rust bindings 스펙이 다룬다. 이 장은 그 계약을 실제 샘플 코드로 보여준다.

Rust에서 zlink를 쓰는 방법을 실제 샘플 코드 중심으로 설명합니다. 메시징 개념은 코어 가이드를 참고하세요.


설치

Cargo.toml에 추가합니다.

[dependencies]
zlink = "11.2"
  • Rust 1.85 이상 (edition 2024).
  • 네이티브 코어가 빌드 시 함께 링크됩니다.
use zlink::{Context, Message, Received, RecvFlags, SendFlags};

5분 예제 — PING/ACK

use zlink::{Context, Message, Received, RecvFlags};

// 서버
let ctx = Context::new().unwrap();
let server = ctx.pair_socket().unwrap();
server.bind("tcp://127.0.0.1:5555").unwrap();

let mut received = Received::empty();
server.recv(&mut received, RecvFlags::NONE).unwrap();
println!("{}", received.parts()[0].as_str().unwrap()); // PING

let ack = Message::try_from(b"ACK").unwrap();
server.send().message(ack).submit_sync(SendFlags::NONE).unwrap();
// 클라이언트
let ctx = Context::new().unwrap();
let client = ctx.pair_socket().unwrap();
client.connect("tcp://127.0.0.1:5555").unwrap();

let ping = Message::try_from(b"PING").unwrap();
client.send().message(ping).submit_sync(SendFlags::NONE).unwrap();

let mut received = Received::empty();
client.recv(&mut received, RecvFlags::NONE).unwrap();
println!("{}", received.parts()[0].as_str().unwrap()); // ACK

핵심 타입

컨텍스트

let ctx = Context::new().expect("context creation failed");
// ctx가 drop되면 하위 소켓의 블로킹 작업이 중단됩니다

메시지

Message는 페이로드 프레임 하나를 소유합니다. send로 넘기면 소유권이 옮겨가(move) 이후 사용을 컴파일러가 막아줍니다.

// 바이트 슬라이스에서 생성
let msg = Message::try_from(b"payload").unwrap();

// 크기 지정 빈 프레임
let mut msg = Message::with_size(256).unwrap();
msg.data_mut().copy_from_slice(&data);

// 전송 — msg는 여기서 move됨
socket.send().message(msg).submit_sync(SendFlags::NONE).unwrap();
// msg를 다시 쓰면 컴파일 에러 → 소유권 안전성을 타입으로 보장

HWM 대기 가능 send는 결과 객체를 돌려주는 submit()과 동기 submit_sync(SendFlags)를 제공합니다. submit()Result<SendSubmission, _>를 돌려주고 SendSubmissionresult (OK|BACKPRESSURED)와 admitted future를 가집니다. async 실행 흐름에서는 socket.send().message(msg).submit()?.admitted.await?로 admission을 기다립니다(resultOK면 이미 완료). plain thread에서는 submit_sync(SendFlags::NONE)을 사용할 수 있고, 즉시 backpressure가 필요하면 SendFlags::DONT_WAIT을 지정합니다.

Request는 reply까지 blocking하는 submit_sync()RequestSubmission(result·admittedreply future 추가)을 돌려주는 submit()을 제공합니다. resultOK면 바로 replyawait하면 되고, reply는 terminal 결과이며 별도 DATA receive가 아닙니다.

Core가 pre-admission operation을 접수한 뒤 retry를 소유하므로 caller retry queue를 만들거나 payload를 재전송하지 않습니다. 공용 native ZLINK_OPT_PENDING_MAX_MSGS/BYTES cap은 pending SEND와 REQUEST에 함께 적용되고 send 전용 pending 이름은 없습니다. Completion은 local admission일 뿐 peer delivery나 application acknowledgement가 아닙니다.

Future를 drop하면 Rust waiter의 대기를 멈출 수 있습니다. Core submit 전에는 builder를 버려 Core를 호출하지 않지만, Core가 payload를 접수한 뒤에는 admission이나 request가 계속될 수 있고 socket owner가 늦은 completion을 drain합니다. Bind/connect 전에 stream.options().set_recv_mode(StreamRecvMode::Raw) 또는 ::Packet을 호출한 뒤 각각 recv 또는 recv_packet을 사용합니다.

public poller가 socket의 POLLCOMPLETION owner이면 blocking request나 Future가 남아 있는 동안 다른 thread가 wait() loop를 계속 실행해야 합니다. wait()가 native completion을 drain해 Rust state를 settle/cleanup하므로 같은 thread에서 wait 사이에 blocking terminal을 호출하면 진행이 멈출 수 있습니다.

수신된 메시지 읽기:

let part = &received.parts()[0];
let bytes: &[u8] = part.as_bytes();
let text: &str = part.as_str().unwrap();   // UTF-8
let size = part.size();

Received — 수신 봉투

let mut received = Received::empty();   // 재사용 가능
socket.recv(&mut received, RecvFlags::NONE).unwrap();

let parts = received.parts();                       // &[Message]
let rid: Option<&RoutingId> = received.routing_id(); // ROUTER/SPOT
let token: Option<&ReplyToken> = received.reply_token();

라우팅 ID

let rid = RoutingId::from(b"server-01");
socket.set_routing_id(&rid).unwrap();

소유권과 수명

Rust의 소유권 시스템이 대부분을 컴파일 타임에 강제합니다.

상황 규칙
submit() 성공 Message가 이미 move됨 — 추가 처리 불필요
submit() 실패 Result::Err 반환, 빌더가 내부 상태 정리
recv() &mut Received로 in-place 수신, drop 시 파트 해제
비동기 요청 회신 Vec<Message> 소유, 각 Message는 drop으로 해제
// 에러 처리 패턴
let msg = Message::try_from(b"data").unwrap();
match socket.send().message(msg).submit_sync(SendFlags::NONE) {
    Ok(_) => { /* 전송됨 */ }
    Err(e) => eprintln!("send failed: {e}"),
}

에러 처리

Rust 바인딩은 작업별 에러 타입을 Result로 돌려줍니다.

match socket.send().message(msg).submit_sync(SendFlags::DONT_WAIT) {
    Ok(_) => {}
    Err(e) => match e.code() {
        zlink::SubmitResult::Backpressured => { /* 재시도 */ }
        zlink::SubmitResult::NotConnected => { /* 연결 없음 */ }
        _ => return Err(e.into()),
    },
}

에러 타입: SubmitError, RequestError, RecvError, BindError, ConnectError, ConfigError, CloseError, HandlerError. 각 타입은 code() 메서드로 결과 코드 enum을 노출합니다.


C API ↔ Rust 대응표

C API Rust API
zlink_ctx_new() Context::new()
zlink_ctx_term() drop(ctx)
zlink_socket(ctx, type) ctx.pair_socket()
zlink_bind(s, ep) socket.bind(ep)
zlink_connect(s, ep) socket.connect(ep)
zlink_send(..., parts, count, ...) / zlink_send_rid(..., parts, count, ...) socket.send().message(m).submit_sync(flags)
DONTWAIT send + completion pull socket.send().message(m).submit()?.admitted.await
zlink_recv(..., parts_out, capacity, count_out, ...) socket.recv(&mut received, flags)
zlink_msg_data(msg) part.as_bytes()
zlink_routing_id_t RoutingId
zlink_socket_monitor_open(...) SocketMonitor::open(&socket)
zlink_poller_new() Poller::new()
zlink_timer_new() Timer::new()

네이티브 라이브러리 / 배포

네이티브 코어는 빌드 시 자동으로 링크됩니다. 런타임 버전 확인:

let (major, minor, patch) = zlink::version();   // (i32, i32, i32) 튜플
println!("zlink {major}.{minor}.{patch}");

스레딩 규칙:

항목 규칙
Context Sync — 스레드 간 공유 가능 (Arc<Context>)
소켓 Send이지만 한 스레드에서만 사용. 동시 접근 금지
Message::as_bytes() 메시지 수명 동안만 유효

submit_sync(SendFlags::NONE)은 HWM admission을 기다리는 동안 호출 thread를 멈춥니다. plain thread에서는 그 thread만 대기합니다. async executor에서 다른 task를 계속 실행해야 하면 submit()?.admitted.await를 사용하고, 즉시 backpressure가 필요하면 submit_sync(SendFlags::DONT_WAIT)을 사용합니다.

use std::sync::Arc;
let ctx = Arc::new(Context::new().unwrap());

let ctx2 = ctx.clone();
std::thread::spawn(move || {
    let socket = ctx2.dealer_socket().unwrap();
    // 이 스레드에서만 socket 사용
});

샘플

bindings/rust/samples/ 디렉터리의 검증된 샘플입니다.

파일 설명
pair_recv_sample.rs PAIR 송수신
dealer_router_recv_sample.rs DEALER/ROUTER 송수신
request_reply_future_sample.rs Future 요청/응답
pubsub_recv_sample.rs PUB/SUB 발행·구독
stream_recv_sample.rs STREAM 원시 TCP
stream_packet_recv_sample.rs STREAM PACKET pull
monitor_recv_sample.rs 모니터 이벤트 수신

SPOT·Actor 예제는 core 바인딩이 아니라 framework 샘플이 다룬다. Rust에는 아직 framework 바인딩이 없다.

cd bindings/rust
cargo run --example pair_recv_sample

API 레퍼런스 생성:

cd bindings/rust
cargo doc --no-deps --open

더 보기

소켓 패턴 - 소켓 패턴 개요PAIR · PUB/SUB · DEALER · ROUTER · STREAM · 프록시

서비스 - Framework 서비스 개요

운영 - 소켓 옵션 · TLS 보안 · 모니터링 · 스레드 안전성 · 메시지 API · 라우팅 ID