iClaustron training for Rust developers · module 9 of 9 · 20 min

Inside the crates

How iClaustron is built, for Rust developers who will read, debug or change it: the rules, the crates, the receive path in code, the C ABI, and the tools that check it all.

The rules · chapter 07, the style guide

C-like Rust: what the style guide allows and what it forbids

Allowed

  • Structs, enums, impl methods, match, plain loops.
  • Result<T, IcError> and ? on every fallible call.
  • Box, Vec, Arc; a mutex at a documented level.
  • unsafe for FFI, libc and words as bytes: small, commented.

Forbidden in library code

  • async: explicit threads and poll are the design.
  • Trait bounds, where, impl Trait; traits beyond the derives.
  • Closures, iterator chains, macros beyond a sanctioned few.
  • unwrap outside tests; Rc, RefCell; shadowing.

The test for every construct: can a C programmer who has read one page of Rust syntax follow it without looking anything up?

In the crates today: no trait is defined and nothing is async. Closures do appear: thread bodies, catch_unwind in ic_capi, the key walks of hash.rs.

iclaustron: doc/rust/07-style-guide.md (Allowed and encouraged; Forbidden in library code); doc/rust/01-goals-and-principles.md (principle 2); crates/ic_util/src/threadpool.rs (ThreadFunc); crates/ic_capi/src/lib.rs (guarded, on_conn); crates/ic_apid/src/hash.rs (KeyWalk)

The rules · chapter 09, licensing

Clean room: written from the wire, MIT licensed, no RonDB name

  1. Listen: capture the traffic, read it with the project's own Wireshark dissector.
  2. Ask the author what the wire does not show.
  3. Read RonDB's source only to settle a fact, never to copy.
  4. Write the fact into chapter 05, in our words and names.
  5. Implement from chapter 05; verify against a live cluster.
  • The crates are MIT. A file translated from legacy-c/ also carries iClaustron AB's copyright line; legacy-c/ stays GPLv2, never built.
  • Every signal and block is named by us, IC_SIG_KEY_OP_REQ = 12; comments cite chapter 05: Verify: chapter 05 §17.5.
  • cargo xtask names reads RonDB's names from a RonDB tree when it runs and looks for each in crates/, tools/, xtask/, include/ and doc/.
  • One crate from crates.io: libc. deny.toml lists the licences allowed.

iclaustron: doc/rust/09-licensing.md (Facts; Clean-room procedure; Names; Repository); crates/ic_signals/src/numbers.rs (IC_SIG_KEY_OP_REQ); xtask/src/names.rs (module note, SCOPE); Cargo.lock; deny.toml; legacy-c/README.md

The crates · from crates/*/Cargo.toml

The crates' dependencies, as their Cargo.toml files declare them

crate · uses → ic_capi ic_apid ic_apic ic_signals ic_comm ic_protocol ic_util ic_port ic_apid ic_apic ic_signals ic_comm ic_protocol ic_util ic_port libc left of the line: the crate itself and the crates above it

A row is one Cargo.toml: a filled square is declared and used, a dashed one declared only. Every mark lies right of the line: no cycle.

Every other crate uses ic_port. libc, the workspace's one crate from crates.io, is used by ic_port and ic_comm alone.

ic_apid uses five crates and declares a sixth; ic_capi uses ic_apid, ic_signals, ic_port. ic_signals and ic_apic do not use each other.

Two declarations go unused in src/: ic_comm's ic_util and ic_apid's ic_protocol.

iclaustron: crates/*/Cargo.toml ([dependencies]); Cargo.toml ([workspace.dependencies]); Cargo.lock (libc); uses counted with grep '<crate>::' in each crate's src/

The crates · where to start reading

What each crate holds, its size, and where to start reading

CrateLinesWhat it holdsStart at
ic_port4 340the OS layer, glib replaced: libc calls, IcMutex, time, IcError, NDB codes, tracingsync.rs, err.rs
ic_util2 649PtrArray, bitmap, memory container, connectstring, thread pool, MD5, XXH3ptr_array.rs
ic_comm2 330a TCP connection, a poll set over epoll or kqueue, the line protocol, a page poolconnection.rs
ic_protocol484base64 and the management protocol's wordsproto_str.rs
ic_apic2 583the management client, the configuration blob, the typed configurationmgm_client.rs
ic_signals8 528signal and block numbers, the header, codecs per signal family, program wordsheader.rs, key_ops.rs
ic_apid31 473the Data API: threads, connections, transactions, scans, executors, programslib.rs, apid_conn.rs
ic_capi7 511the C ABI in one file: 179 exported functionslib.rs, its opening note

Lines are wc -l of src/, tests included. Each lib.rs names its modules. MODULE.md maps a crate to its C; ic_apic's, ic_signals' and ic_apid's are still the phase 0 template.

iclaustron: crates/*/src/lib.rs (module notes); crates/*/MODULE.md; crates/ic_capi/src/lib.rs (#[no_mangle] exports); wc -l crates/*/src/*.rs

ic_port · err.rs and ndb_err.rs

IcError is one i32 code; category and severity come from it

#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[repr(C)]
pub struct IcError {
  pub code: i32,
}

impl IcError {
  pub fn category(&self) -> ErrorCategory {
    if self.is_ic_error() {
      return ic_class(self.code).0;
    }
    crate::ndb_err::class_of(self.code).category()
  }
  // ... severity(), is_temporary(), message()
  • Every fallible call returns Result<T, IcError>; the C ABI hands C the code itself.
  • Three spaces in one i32: 7000 to 7166 are iClaustron's, small positive codes the OS's errno, the rest NDB's.
  • ic_class sorts iClaustron's codes; ndb_err.rs holds NDB's classes. 7129, a panic, is Internal and Stop.
  • It lives in ic_port, below ic_util: the port layer's own calls fail with it.

iclaustron: crates/ic_port/src/err.rs (IcError, impl IcError, ic_class, IC_FIRST_ERROR, IC_LAST_ERROR, ErrorCategory, ErrorSeverity); ndb_err.rs (ErrorClass, class_of); crates/ic_port/src/lib.rs (module note)

ic_port · sync.rs

IcMutex owns its data and a level; debug builds check the order

pub struct IcMutex<T> {
  level: u32,
  inner: Mutex<T>,
}
fn check_order(level: u32) {
  if level == IC_MUTEX_LEVEL_UNORDERED || !cfg!(debug_assertions) {
    return;
  }
  // ... violated: a level this thread holds, >= level
  if violated != 0 {
    // ... print both levels
    std::process::abort();
  }
}
  • The value lives inside: lock() gives a guard, the only way to it, released when dropped. IcCond::wait takes the guard and gives it back.
  • Each lock pushes its level on a thread-local stack. Locking at or below a level held aborts a debug build; a release build skips the check.
  • Levels run from 1, a table's AUTO_INCREMENT range, to 11, a completion slot; 0 is never checked. Module 02 has the table.

iclaustron: crates/ic_port/src/sync.rs (IcMutex, check_order, push_level, IcMutexGuard, IcCond::wait, IC_MUTEX_LEVEL_*); crates/ic_port/MODULE.md (Deviations; Rust notes for C readers)

ic_util · ptr_array.rs and mc.rs

PtrArray ids: a 24-bit slot and a 40-bit generation, a 32-bit word on the wire

pub fn remove(&mut self, id: PtrId) -> Option<T> {
  let index = id.index() as usize;
  let slot = self.slots.get_mut(index)?;
  if slot.generation == IC_RETIRED || slot.generation != id.generation() {
    return None;
  }
  let value = slot.value.take()?;
  // ...
  // A slot whose generation would wrap is retired, never used again, ...
  if slot.generation >= IC_MAX_GENERATION {
    slot.generation = IC_RETIRED;
    return Some(value);
  }
  slot.generation += 1;
  self.free_list.push(index as u32);
  Some(value)
}
  • An id is index | generation << 24 in a u64. Generations start at 1, so a valid id is never 0, PtrId::NONE, which means no object.
  • A slot whose generation would wrap is retired: no id ever names a second object. Slots are reused last freed, first.
  • A signal carries wire_word(), the index and eight bits of the generation; id_of_wire finds the live object again, and a reply is checked against its transaction's id too.
  • MemoryContainer hands out handles, not pointers: buffer, offset, length. No other crate uses it today.

iclaustron: crates/ic_util/src/ptr_array.rs (module note; PtrId, wire_word, PtrArray::insert, remove, id_of_wire); mc.rs (McHandle); crates/ic_util/MODULE.md (Deviations); crates/ic_capi/src/lib.rs (opening note)

ic_signals · the wire format

ic_signals is the wire alone: no sockets, no threads, no state

/// The eight words of signal data. The column data length, third, is
/// zero: in the long form it is the length of section 1.
pub fn encode(&self) -> [u32; IC_KEY_OP_REQ_LEN] {
  [
    self.coord_ptr,
    self.query_ref,
    0,
    self.table_id,
    self.flags_word,
    self.table_version,
    self.trans_id_lo,
    self.trans_id_hi,
  ]
}
  • A request encodes to a fixed array, [u32; N]. header::decode gives a Message<'_> that borrows the words it read.
  • 16 modules on ic_port alone; 131 unit tests run them with no cluster, MySQL's own words from captures among them.
  • Fuzz targets for its decoders are planned in chapter 08; none is in the tree yet.

iclaustron: crates/ic_signals/src/key_ops.rs (KeyOpReq::encode); header.rs (decode, Message); lib.rs (module note); interp.rs and scans.rs (tests); doc/rust/08-testing-and-tooling.md (Unit tests)

ic_apid · the module map

ic_apid's 32 files, grouped by the job they do

JobFilesRead first
Threads and linksapid_global, rec_thread, send_pool, heartbeat, connect_thread, node_connect, node_state, handshake, adaptive_sendapid_global.rs: start_with_options
Receive pathsignal_reader, signal_page, thread_conn, fragmentsthread_conn.rs, its module note
The connectionapid_conn, transaction, query, key_opapid_conn.rs: poll_with_requests
Tables and rowsdict_client, dict_cache, record, row_codec, hash, collation_client, collation_map, text_row, auto_incrementrecord.rs, then row_codec.rs
Scansscan, index_scan, index_order, scan_readerscan.rs, its module note
Executors, programsexecutor, programexecutor.rs: Runner::run

Every file opens with a //! note; a translated one names its legacy-c/ file. The largest: scan.rs, 3 442 lines; apid_conn.rs, 2 580.

iclaustron: crates/ic_apid/src/lib.rs (module list); the module note of each file; wc -l crates/ic_apid/src/*.rs

ic_apid · receive path

The receive path in code: reader, router, page, inbox, poll

receive thread SignalReader signal_reader.rs read_from(&Connection) bytes into u32 words ReceivePage: Arc<Vec<u32>>, 128 KB never byte-swapped route_signals rec_thread.rs header::decode, borrowed by receiver block route_view: copied route_by_reference: ≥ 256 words, in place SignalPage signal_page.rs one user thread's signals of a round 4 head words each, then data, or where it lies inbox, shared ThreadConnection thread_conn.rs Router::post_all post: one lock a round wakes after unlocking exchange_with_requests user thread ApidConnection apid_conn.rs poll_with_requests signal_page::next → SignalView<'a> receive_view → route_reply emptied pages back, on the next poll

SignalReader::read_from reads a socket's bytes straight into the u32 words of a receive page, never swapped, so reading a word costs nothing.

route_signals decodes each header where it lies and routes by receiver block: a small signal is copied, a large one held in its page.

A SignalPage gathers one user thread's signals for the round: four words of header each, then the data or where it lies.

Router::post_all ends the round: ThreadConnection::post swaps the page into the inbox under one lock, then wakes the thread.

poll_with_requests takes the pages, walks each with signal_page::next and hands every SignalView to receive_view.

iclaustron: crates/ic_apid/src/signal_reader.rs (SignalReader::read_from, ReceivePage, IC_RECEIVE_PAGE_WORDS); rec_thread.rs (route_signals); signal_page.rs (module note, next); thread_conn.rs (Router::post_all, ThreadConnection::post); apid_conn.rs (poll_with_requests, receive_view)

ic_apid · node_connect.rs and apid_conn.rs

SignalView borrows its words: poll reads signals where they lie

pub struct SignalView<'a> {
  pub sig_no: u16,
  // ... the two blocks, the sending node, fragment_state
  pub data: &'a [u32],
  pub sections: [&'a [u32]; header::IC_MAX_SECTIONS],
  pub num_sections: usize,
}

for page in &pages {
  let mut at: usize = 0;
  while let Some(signal) = signal_page::next(page, &mut at) {
    taken += 1;
    self.receive_view(&signal);
  }
}
  • A SignalView is Copy: a few numbers and slices into the page. Decoding a reply reads the words in place.
  • Only what must outlive the page is copied, with to_owned: a fragment waiting for its train, a reply a request waits for.
  • The pages go back to the inbox on the next poll, emptied; a page keeps its allocation, sized by the largest round.

iclaustron: crates/ic_apid/src/node_connect.rs (SignalView, to_owned); apid_conn.rs (poll_with_requests, receive_view); signal_page.rs (module note, next); thread_conn.rs (exchange)

ic_apid · apid_conn.rs

ApidConnection: one thread's objects, held with no lock

pub struct ApidConnection {
  pub(crate) shared: Arc<ApidShared>,
  inbox: Arc<ThreadConnection>,
  expectations: Expectations,
  coord_records: Vec<CoordRecord>,
  free_coord: Vec<Vec<u32>>,
  pub(crate) queries: PtrArray<ApidQuery>,
  pub(crate) transactions: PtrArray<Transaction>,
  pub(crate) executed: VecDeque<QueryId>,
  active: Vec<Option<TransId>>,
  outgoing: Vec<Outgoing>,
  pub(crate) scans: PtrArray<Scan>,
  pub(crate) receivers: PtrArray<ReceiverSlot>,
  // ...
  • One thread uses it, so none of its 49 fields is locked. Only the inbox is shared, with the receive threads.
  • Queries, transactions, scans and receivers sit in PtrArrays, named by ids; coordinator records at fixed places, active[] by the same place.
  • Requests packed at define wait per node in Outgoing until the send. An executor's Runner owns an ApidConnection like any user thread.

iclaustron: crates/ic_apid/src/apid_conn.rs (ApidConnection, CoordRecord, Outgoing, Staged, coord_index_of); executor.rs (Runner, Runner::run)

ic_apid · row_codec.rs and hash.rs

row_codec and hash: a row into words, a key onto a node

row_codec.rs: a record's row and the words

  • key_info_into: each key column in attribute order, padded to whole words.
  • read_attr_info: every column, 0xFFF0, or a bitmask of them, 0xFFF3.
  • unpack_row: a result bitmap, then the values; a NULL takes no room.

hash.rs: a key, its partition, a node

  • distribution_key: its columns padded to words, a character column by its collation.
  • partition_hash: MD5's second word for a table from before 22.10.1, else XXH3's high half.
  • choose_node: a read-backup table's started replicas in turn, else the primary.

Both work on plain slices, rows as &[u8] and words as &[u32]. A wrong node costs a hop in the cluster, never a wrong answer.

iclaustron: crates/ic_apid/src/row_codec.rs (module note, key_info_into, read_attr_info, unpack_row, IC_COL_READ_EVERY, IC_COL_READ_MASKED); hash.rs (module note, distribution_key, partition_hash, choose_node, primary_of); crates/ic_util/src/lib.rs (md5, xxh3)

unsafe · counted with grep

Where unsafe lives: counted per crate, with what it is for

Crateunsafe blocksWhere, and what for
ic_capi375C's pointers: handles, out pointers, sized structs, callbacks. 174 of the 179 exports are unsafe extern "C" fn, with 32 unsafe helpers.
ic_comm19connection.rs, 11: socket, bind, connect, socket options, poll, recv; poll_set.rs, 8: epoll and kqueue
ic_port13daemon.rs, 8: fork, signal handlers, _exit; socket.rs, 4: fcntl, close; time.rs, 1: getrusage
ic_apid3words seen as bytes: signal_reader.rs, 2, to read a socket into a page; node_connect.rs, 1, to write words out
the other four0ic_signals, ic_util, ic_protocol, ic_apic

410 blocks, 410 SAFETY: comments. The row path, record.rs, row_codec.rs and query.rs, has no unsafe block: a row is the query's own Vec<u8>.

iclaustron: grep 'unsafe {' and 'SAFETY' in crates/*/src; crates/ic_apid/src/signal_reader.rs (bytes_of, bytes_of_mut); node_connect.rs (words_as_bytes); query.rs (module note); doc/rust/07-style-guide.md (unsafe blocks)

ic_capi · the C ABI

ic_capi exports: a Rust object boxed as a handle, or named by id

#[no_mangle]
pub unsafe extern "C" fn ic_apid_global_create_connection(
  global: *mut IcApidGlobal,
  conn: *mut *mut IcApidConnection,
) -> i32 {
  // ... the null checks, and g: the global
  guarded(|| match g.create_connection() {
    Ok(made) => {
      let handle = Box::into_raw(Box::new(made)) as *mut IcApidConnection;
      // SAFETY: checked non-null above; the caller's promise.
      unsafe { conn.write(handle) };
      0
    }
    Err(e) => e.code,
  })
  • IC_APID_CONNECTION is an opaque struct; the handle is the boxed ApidConnection, given back to Box::from_raw by ic_apid_conn_free.
  • Queries, transactions, scans and scan batches cross as their 64-bit ids, never 0; ic_apid_scan_batch_scan(conn, batch) asks the connection for a batch's scan.
  • cbindgen writes include/iclaustron/ic_apid.h from lib.rs and cbindgen.toml; cargo xtask header diffs it with the copy checked in.

iclaustron: crates/ic_capi/src/lib.rs (ic_apid_global_create_connection, ic_apid_conn_free, IcApidConnection, guarded); crates/ic_capi/cbindgen.toml; crates/ic_capi/MODULE.md (How a call crosses); xtask/src/main.rs (run_header)

ic_capi · panics and sizes

The C boundary's guards: panics become 7129, short structs refused

unsafe fn on_conn<T, F: FnOnce(&mut ApidConnection) -> T>(
  conn: *mut IcApidConnection,
  f: F,
) -> Result<T, i32> {
  let raw = conn as *mut ApidConnection;
  // ... a null handle: IC_ERROR_NULL_POINTER
  let c = unsafe { &mut *raw };
  if c.is_unusable() {
    return Err(err::IC_ERROR_INTERNAL_PANIC);
  }
  match catch_unwind(AssertUnwindSafe(|| f(c))) {
    Ok(value) => Ok(value),
    Err(_) => {
      unsafe { (*raw).set_unusable() };
      Err(err::IC_ERROR_INTERNAL_PANIC)
  • 134 of the 179 exports run under catch_unwind, through on_conn on a connection or guarded; the other 45 are getters, setters and _inits.
  • A panic may leave a connection half changed, so it is marked unusable: every later call but its free returns 7129.
  • sized_from_c reads an argument struct only if its size field covers this header's struct, else 7020; options are read field by field, get_sized.

iclaustron: crates/ic_capi/src/lib.rs (on_conn, guarded, sized_from_c, get_sized, put_sized); crates/ic_apid/src/apid_conn.rs (set_unusable, is_unusable); crates/ic_capi/cbindgen.toml (Panics; Structs with a size)

Testing · chapter 08, as built

The test pyramid: unit tests, captured words, a live cluster, fuzzing

unit tests539 in the crates' src/ · no cluster needed cargo test --workspace captured wordsMySQL's own words, from captures tools/scan_capture.sh, interp_capture.sh integration116 tests · a live cluster cargo xtask test-integration fuzzing9 tests · the decoders cargo xtask fuzz

539 unit tests in the crates' src/, 251 in ic_apid and 134 in ic_signals, run by cargo test with no cluster.

Captured words: what MySQL sent, from tcpdump captures read with the dissector, asserted word for word, the pcap and frame named.

116 integration tests, 108 in ic_apid and 8 through the C ABI, against a live cluster; without IC_TEST_CONNECTSTRING each returns at once.

Fuzzing: nine fuzz_ tests, #[ignore]d by cargo test, give mutated seeds to the decoders of what comes from the network through ic_port::fuzz; a case fails on a panic, an overflow or a second's run.

iclaustron: grep '#[test]' in crates/*/src; crates/ic_apid/tests/integration.rs and crates/ic_capi/tests/c_api.rs (module notes); crates/ic_signals/src/interp.rs (tests), fuzz_tests.rs; crates/ic_port/src/fuzz.rs; tools/scan_capture.sh; xtask/src/main.rs (run_test_integration, run_fuzz); doc/rust/08-testing-and-tooling.md

Tooling · xtask/ and the files at the root

cargo xtask and the checked-in configs: the checks before a commit

cargo xtask check               # fmt, clippy, test, style
cargo xtask style               # 80 columns, forbidden constructs
cargo xtask header              # cbindgen, diffed with the header
cargo xtask test-integration    # against a live cluster
cargo xtask fuzz [target]       # the decoders' fuzz tests
cargo xtask names [rondb-tree]  # no RonDB name in the tree
cargo xtask install [prefix]    # library, header, pkg-config
cargo xtask tags                # vim tags, Rust and legacy-c/
  • check runs cargo fmt --check, clippy with -D warnings, cargo test --workspace, then style.
  • style looks in code only for async fn, Rc<, RefCell<, where, .unwrap() and six more; style-allow.txt names 5 exceptions.
  • rust-toolchain.toml takes the stable channel, rustfmt.toml 80 columns and 2 spaces; the workspace lints deny missing_safety_doc.

iclaustron: xtask/src/main.rs (print_help, run_check, FORBIDDEN, run_style, run_fuzz); xtask/style-allow.txt; rust-toolchain.toml; rustfmt.toml; Cargo.toml ([workspace.lints], [profile.fuzz]); clippy.toml; deny.toml

Tooling · tools/ic_bench and tools/ic_model

ic_bench measures, ic_model predicts a change before it is built

ic_bench: measure

  • Primary key reads or updates: threads, batches in flight, or callers through executors.
  • Built with count-allocs it counts allocations: 5 per 1 000 reads, one a batch, the benchmark's own. The library's path allocates nothing per read.

ic_model: predict

  • A discrete-event model: user threads, receive threads, data nodes, each cost in ns.
  • Calibrated on six measured runs: within 14 % in rate, 22 % in latency.
  • Set what a change would move, sweep it, and see which server reaches 1.

Both on the author's Mac with two release data nodes on it: allocations counted 2026-10-14, 100 000 keys; the model fitted 2026-09-24.

iclaustron: tools/ic_bench/src/main.rs (module note); alloc_count.rs; doc/rust/06-phases.md (Phase 7, the performance pass); tools/perf_pass.sh; tools/ic_model/src/main.rs; doc/rust/12-performance-model.md (Calibration; Using it)

History · chapter 06, phases

How it was built: translated foundations, then new code by phase

PhaseWhat it builtMostly
0legacy-c/ set aside, MIT, empty crates, the C header rewritten firstbootstrap
1–2ic_port, ic_util, ic_comm, ic_protocol; ic_apic, the configurationtranslated from the C
3–5threads, membership, node failure; dictionary, records; transactions, key operationsnew code
6our own names, the Wireshark dissector, unique keys, every row size, 4012new code
7the C ABI, the user guide, the examples in Rust and C: release 0.1packaging
8–10executors, scans, character sets; programs, AUTO_INCREMENT; scans completednew code, 0.2 and 0.3
11hardening, and what early applications needopen

Phases 0 to 5, sized at 20 weeks, were built from 2026-09-18 to 2026-09-25. A translated module keeps the C's file and function names.

iclaustron: doc/rust/06-phases.md (phase table; Replanned 2026-10-08; Working method per module); doc/rust/01-goals-and-principles.md (principles 1 and 11); git log

History · from legacy-c/ to the crates

A worked mapping: post_ndb_messages in C, ThreadConnection::post

C · legacy-c/api/ic_apid_rec_thread.ic

Rust · crates/ic_apid/src/thread_conn.rs

ic_mutex_lock(loc_thd_conn->mutex);
/* Link it into list on this thread connection object */
/* ... */
loc_thd_conn->last_received_message= last_ndb_message_page;
if (loc_thd_conn->thread_wait_cond)
{
  loc_thd_conn->thread_wait_cond= FALSE;
  signal_flag= TRUE;
}
ic_mutex_unlock(loc_thd_conn->mutex);
/* END Critical Section with delayed wake up signal */
if (signal_flag)
{
  ic_cond_signal(loc_thd_conn->cond);
}
let mut inbox = self.inbox.lock();
if inbox.closed {
  page.clear();
  return false;
}
let fresh = inbox.spare.pop().unwrap_or_default();
let full = std::mem::replace(page, fresh);
inbox.pages.push(full);
let wake = inbox.waiting;
inbox.waiting = false;
// Unlock, and only then wake; see the note at the top of the file.
drop(inbox);
if wake {
  self.cond.signal();
}

The same order: lock, append, note a sleeper, unlock, then wake. The unlock is drop(inbox); the list of pages is a Vec, a spare swapped in.

iclaustron: legacy-c/api/ic_apid_rec_thread.ic (post_ndb_messages); crates/ic_apid/src/thread_conn.rs (module note, ThreadConnection::post); doc/rust/11-c-to-rust-mappings.md (Threads and synchronisation); doc/rust/03-module-map.md

Recap · module 09

Inside the crates: what to take away

  • C-like Rust, from the wire in a clean room: no trait of our own, nothing async.
  • Eight crates, no cycle: the rest all use ic_port, ic_signals nothing else; libc is the one from outside.
  • Errors are one i32; mutexes carry levels; queries, transactions, scans are ids with a generation.
  • Replies go reader, router, page, inbox, poll, and are read in place as a SignalView.
  • unsafe sits at the edges, 410 blocks with as many SAFETY notes; the row path has none.
  • The C ABI boxes objects as handles and turns a panic into 7129.

iclaustron: doc/rust/07-style-guide.md; doc/rust/09-licensing.md; crates/*/Cargo.toml; crates/ic_port/src (err.rs, sync.rs); crates/ic_util/src/ptr_array.rs; crates/ic_apid/src; crates/ic_capi/src/lib.rs

iClaustron training for Rust developers · 9 modules · you are at 09

End of the iClaustron training · back to 01