Rust 바인딩 가이드 (zlink)¶
이 장의 계약 소유 문서 — Rust bindings 스펙이 다룬다. 이 장은 그 계약을 실제 샘플 코드로 보여준다.
Rust에서 zlink를 쓰는 방법을 실제 샘플 코드 중심으로 설명합니다. 메시징 개념은 코어 가이드를 참고하세요.
설치¶
Cargo.toml에 추가합니다.
- Rust 1.85 이상 (edition 2024).
- 네이티브 코어가 빌드 시 함께 링크됩니다.
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
핵심 타입¶
컨텍스트¶
메시지¶
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, _>를 돌려주고 SendSubmission은 result
(OK|BACKPRESSURED)와 admitted future를 가집니다. async 실행 흐름에서는
socket.send().message(msg).submit()?.admitted.await?로 admission을 기다립니다(result가
OK면 이미 완료). plain thread에서는 submit_sync(SendFlags::NONE)을 사용할 수 있고, 즉시
backpressure가 필요하면 SendFlags::DONT_WAIT을 지정합니다.
Request는 reply까지 blocking하는 submit_sync()과 RequestSubmission(result·admitted에
reply future 추가)을 돌려주는 submit()을 제공합니다. result가 OK면 바로 reply를
await하면 되고, 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¶
소유권과 수명¶
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 바인딩이 없다.
API 레퍼런스 생성:
더 보기¶
소켓 패턴 - 소켓 패턴 개요 — PAIR · PUB/SUB · DEALER · ROUTER · STREAM · 프록시
서비스 - Framework 서비스 개요