Expand description
Safe Rust client API for GemStone/S over GCI.
This crate builds on gemstone-gci, which owns the dynamic libgcirpc
loading and raw ABI calls. Session provides RAII login/logout behavior,
explicit OOP values, conservative transaction helpers, and basic value
marshalling for nil, booleans, small integers, characters, and raw OOPs.
Rust code imports this crate as gemstone_rs:
use gemstone_rs::{Config, Session, Value};
fn main() -> gemstone_rs::Result<()> {
let config = Config::from_env()?;
let mut session = Session::login(config)?;
let value = session.eval("3 + 4")?;
assert_eq!(value, Value::SmallInt(7));
session.logout()?;
Ok(())
}Build configuration explicitly when you do not want to read process environment:
use gemstone_rs::{Config, Session};
fn main() -> gemstone_rs::Result<()> {
let config = Config::builder()
.stone("gs64stone")
.host("localhost")
.netldi("netldi")
.username("DataCurator")
.password("your-password")
.build()?;
let mut session = Session::login(config)?;
println!("session id: {}", session.session_id());
Ok(())
}Store and fetch values through UserGlobals:
use gemstone_rs::{Config, Session};
fn main() -> gemstone_rs::Result<()> {
let mut session = Session::login(Config::from_env()?)?;
let text = session.new_string("hello from Rust")?;
session.global_put("GemStoneRsDocExample", text)?;
session.commit()?;
let stored = session.global_get("GemStoneRsDocExample")?;
assert_eq!(session.fetch_string(stored)?, "hello from Rust");
Ok(())
}Store a mapped Rust payload under the default bridge-root dictionary:
use gemstone_rs::{BridgeValue, Config, Session};
fn main() -> gemstone_rs::Result<()> {
let mut session = Session::login(Config::from_env()?)?;
let payload = BridgeValue::dictionary([
("name".to_string(), BridgeValue::from("Tariq")),
("amount".to_string(), BridgeValue::from(100_i64)),
("currency".to_string(), BridgeValue::from("GBP")),
]);
let mut bridge_root = session.bridge_root()?;
bridge_root.put("MyTestDict", payload)?;
bridge_root.commit()?;
Ok(())
}Add a typed manual mapping on top of BridgeValue when you want Rust
structs at the application boundary:
use gemstone_rs::{BridgeDictionary, BridgeMapped, BridgeValue, Config, Session};
struct BookingDraft {
name: String,
amount: i64,
}
impl BridgeMapped for BookingDraft {
fn to_bridge_value(&self) -> BridgeValue {
BridgeValue::dictionary([
("name".to_string(), BridgeValue::from(self.name.clone())),
("amount".to_string(), BridgeValue::from(self.amount)),
])
}
fn from_bridge_dictionary(dictionary: &mut BridgeDictionary<'_>) -> gemstone_rs::Result<Self> {
Ok(Self {
name: dictionary.at_string("name")?,
amount: dictionary.at_smallint("amount")?,
})
}
}
fn main() -> gemstone_rs::Result<()> {
let mut session = Session::login(Config::from_env()?)?;
let mut bridge_root = session.bridge_root()?;
let draft = BookingDraft { name: "Tariq".to_string(), amount: 100 };
bridge_root.put_mapped("BookingDraft", &draft)?;
let loaded: BookingDraft = bridge_root.get_mapped("BookingDraft")?;
assert_eq!(loaded.amount, 100);
Ok(())
}Browse dictionaries, classes, protocols, methods, and source through the same browser API used by the CLI and explorer:
use gemstone_rs::{browser::Browser, Config, Session};
fn main() -> gemstone_rs::Result<()> {
let mut session = Session::login(Config::from_env()?)?;
let mut browser = Browser::new(&mut session);
let dictionaries = browser.dictionaries()?;
let classes = browser.classes("UserGlobals")?;
let protocols = browser.protocols("Object", false, "")?;
let methods = browser.methods("Object", "-- all --", false, "")?;
let source = browser.source("Object", "printString", false, "")?;
println!("{dictionaries:?} {classes:?} {protocols:?} {methods:?}");
println!("{source}");
Ok(())
}Call selectors directly when generated wrappers would be too much:
use gemstone_rs::{Config, Session};
fn main() -> gemstone_rs::Result<()> {
let mut session = Session::login(Config::from_env()?)?;
let seven = session.smallint_oop(7);
let printed = session.perform_oop(seven, "printString", &[])?;
assert_eq!(session.fetch_string(printed)?, "7");
Ok(())
}Transactions are explicit. transaction commits on success and aborts on
error:
use gemstone_rs::{Config, Session};
fn main() -> gemstone_rs::Result<()> {
let mut session = Session::login(Config::from_env()?)?;
session.transaction(|session| {
let value = session.new_string("hello from Rust")?;
session.global_put("GemStoneRsExample", value)
})?;
Ok(())
}OOPs and values are explicit:
use gemstone_rs::{Config, Session, Value};
fn main() -> gemstone_rs::Result<()> {
let mut session = Session::login(Config::from_env()?)?;
let oop = session.value_to_oop(&Value::SmallInt(7))?;
let value = session.perform(oop, "printString", &[])?;
println!("{value:?}");
Ok(())
}Codegen configs can be parsed and checked from Rust:
use gemstone_rs::codegen;
let config = codegen::Config::parse(
"output = generated/gemstone_wrappers.rs\nclass = Object\nmethod = Object>>printString | return=String\n",
None,
)?;
let generated = codegen::generate(&config);
assert!(generated.source.contains("pub struct Object"));Check generated wrappers in build or release tooling:
use gemstone_rs::codegen;
fn main() -> codegen::Result<()> {
let config = codegen::Config::from_file("examples/codegen/gemstone-rs.codegen")?;
let report = codegen::check(&config)?;
assert!(report.up_to_date, "{} is stale", report.output.display());
Ok(())
}Safety notes:
Sessionis deliberately notSendorSync; keep it on the thread that logged in.- Unsafe C ABI calls are isolated in
gemstone-gci. libgcirpcis loaded dynamically at runtime throughGS_LIB_PATH,GS_LIB, or the platform loader path.
Roadmap:
- Broader typed wrapper generation for Rust services and CLIs.
- A richer local explorer over the same API.
- Optional VS Code webview integration once the CLI and explorer contracts are stable.
Re-exports§
pub use bridge::BridgeDictionary;pub use bridge::BridgeFieldRead;pub use bridge::BridgeFieldWrite;pub use bridge::BridgeKey;pub use bridge::BridgeKeyType;pub use bridge::BridgeMapped;pub use bridge::BridgeRoot;pub use bridge::BridgeValue;pub use bridge::DEFAULT_BRIDGE_ROOT;