inchi_sys/lib.rs
1//! Low-level, `unsafe` FFI bindings to the vendored IUPAC InChI 1.07 reference
2//! C library.
3//!
4//! This crate is the unsafe foundation underneath the safe [`inchi`] crate. It
5//! exposes the raw C structs and `extern "C"` entry points (`GetINCHI`,
6//! `GetINCHIKeyFromINCHI`, `MakeINCHIFromMolfileText`, `GetStructFromINCHI`,
7//! the matching `Free*` deallocators, ...) exactly as declared in the upstream
8//! `inchi_api.h`. The native C source is vendored and statically linked, so no
9//! system InChI installation is required.
10//!
11//! Almost all users should depend on the high-level [`inchi`] crate instead.
12//! Everything here is `unsafe` and mirrors C ownership rules: any `out`
13//! parameter populated by a `Get*`/`Make*` call **must** be released with the
14//! corresponding `Free*` call to avoid leaking memory allocated by the C side.
15//!
16//! [`inchi`]: https://docs.rs/inchi
17//!
18//! # Bindings
19//!
20//! By default the version-controlled, pre-generated bindings (produced by
21//! `bindgen`) are used, so no `libclang` is needed to build. Enable the
22//! `regenerate-bindings` feature to regenerate them from the vendored headers
23//! at build time.
24//!
25//! # Safety & threading
26//!
27//! The InChI C library keeps some `static` internal state and is **not**
28//! guaranteed to be thread-safe across concurrent calls. Callers of this crate
29//! are responsible for synchronization; the [`inchi`] crate provides it.
30#![allow(non_upper_case_globals)]
31#![allow(non_camel_case_types)]
32#![allow(non_snake_case)]
33#![allow(clippy::all)]
34
35#[cfg(feature = "regenerate-bindings")]
36mod bindings {
37 include!(concat!(env!("OUT_DIR"), "/bindings.rs"));
38}
39
40#[cfg(not(feature = "regenerate-bindings"))]
41#[path = "bindings.rs"]
42mod bindings;
43
44pub use bindings::*;
45
46#[cfg(test)]
47mod smoke {
48 //! Proves the native library links and produces a correct InChI for a
49 //! trivial structure built directly into an `inchi_Input`.
50 use super::*;
51 use std::ffi::CStr;
52 use std::os::raw::c_char;
53
54 /// A V2000 molfile for methane (single carbon; InChI adds implicit H).
55 const METHANE_MOLFILE: &str = "\n inchi-sys\n\n 1 0 0 0 0 0 0 0 0 0999 V2000\n 0.0000 0.0000 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0\nM END\n";
56
57 #[test]
58 fn methane_via_molfile_text() {
59 // Build NUL-terminated inputs for the C API.
60 let moltext = std::ffi::CString::new(METHANE_MOLFILE).expect("no interior NUL");
61 let options = std::ffi::CString::new("").expect("no interior NUL");
62
63 let mut out: inchi_Output = unsafe { std::mem::zeroed() };
64 let rc = unsafe {
65 MakeINCHIFromMolfileText(moltext.as_ptr(), options.as_ptr() as *mut c_char, &mut out)
66 };
67
68 // 0 == okay, 1 == warning; both yield a valid InChI string.
69 assert!(
70 rc == inchi_Ret_OKAY as i32 || rc == inchi_Ret_WARNING as i32,
71 "MakeINCHIFromMolfileText returned {rc}"
72 );
73 assert!(!out.szInChI.is_null(), "no InChI produced");
74
75 let inchi = unsafe { CStr::from_ptr(out.szInChI) }
76 .to_str()
77 .expect("InChI is valid UTF-8")
78 .to_owned();
79
80 // Release everything the C side allocated before asserting.
81 unsafe { FreeINCHI(&mut out) };
82
83 assert_eq!(inchi, "InChI=1S/CH4/h1H4");
84 }
85}