Skip to main content

coolprop_sys/
lib.rs

1//! [<img alt="GitHub" src="https://img.shields.io/badge/github-8da0cb?style=for-the-badge&labelColor=555555&logo=github" height="22">](https://github.com/portyanikhin/rfluids)
2//! [<img alt="docs.rs" src="https://img.shields.io/badge/docs.rs-66c2a5?style=for-the-badge&labelColor=555555&logo=docs.rs" height="22">](https://docs.rs/coolprop-sys)
3//! [<img alt="crates.io" src="https://img.shields.io/crates/v/coolprop-sys?style=for-the-badge&logo=rust&labelColor=555555&color=fc8d62" height="22">](https://crates.io/crates/coolprop-sys)
4//! [<img alt="CI" src="https://img.shields.io/github/actions/workflow/status/portyanikhin/rfluids/ci.yml?style=for-the-badge&logo=githubactions&logoColor=ffffff&label=ci&labelColor=555555" height="22">](https://github.com/portyanikhin/rfluids/actions/workflows/ci.yml)
5//!
6//! Raw FFI bindings to [`CoolProp`](https://coolprop.org)
7//!
8//! ## Supported platforms
9//!
10//! - `Linux AArch64`
11//! - `Linux x86-64`
12//! - `macOS AArch64`
13//! - `macOS x86-64`
14//! - `Windows AArch64`
15//! - `Windows x86-64`
16//!
17//! ## MSRV
18//!
19//! `coolprop-sys` requires `rustc` 1.85.0 or later.
20//!
21//! ## How to install
22//!
23//! Add this to your `Cargo.toml`:
24//!
25//! ```toml
26//! [dependencies]
27//! coolprop-sys = "8"
28//! ```
29//!
30//! Or via command line:
31//!
32//! ```shell
33//! cargo add coolprop-sys
34//! ```
35//!
36//! 🎁 It comes with native `CoolProp` dynamic libraries for supported platforms. The library
37//! required for your platform will be automatically copied to the target directory during build.
38//!
39//! It also includes pre-generated FFI bindings, so `libclang` is not required for normal builds.
40//!
41//! ### Regenerating bindings
42//!
43//! If you need to regenerate the FFI bindings (requires `libclang`), enable the
44//! **`regen-bindings`** feature.
45//!
46//! Add this to your `Cargo.toml`:
47//!
48//! ```toml
49//! [dependencies]
50//! coolprop-sys = { version = "8", features = ["regen-bindings"] }
51//! ```
52//!
53//! Or via command line:
54//!
55//! ```shell
56//! cargo add coolprop-sys --features regen-bindings
57//! ```
58//!
59//! ## Accessing the native library
60//!
61//! Use the process-wide [`COOLPROP`] handle:
62//!
63//! ```rust
64//! use coolprop_sys::COOLPROP;
65//!
66//! let coolprop = COOLPROP.shared_access();
67//! let critical_temperature = unsafe { coolprop.Props1SI(c"Water".as_ptr(), c"Tcrit".as_ptr()) };
68//! assert!(critical_temperature.is_finite());
69//! ```
70//!
71//! - Use [`shared_access()`](CoolPropLib::shared_access) only for native operations known to
72//!   support concurrent execution.
73//! - Use [`exclusive_access()`](CoolPropLib::exclusive_access) for configuration and debug changes,
74//!   global error or warning handling, `REFPROP` operations, `VTPR` construction or reload, tabular
75//!   backends, and operations whose concurrency guarantees are unknown. When in doubt, use
76//!   exclusive access.
77//!
78//! Some native functions report failure through a sentinel value and store details in the
79//! process-global `errstring`. After such a failure with shared access, release the shared guard,
80//! then acquire exclusive access. If the caller needs error details for that operation, read and
81//! discard the stale `errstring` with
82//! [`get_global_param_string`](bindings::CoolProp::get_global_param_string) (which clears it),
83//! repeat the complete native call, and read the new `errstring` before releasing the exclusive
84//! guard. If the caller does not need error details, clear the stale `errstring` before releasing
85//! the exclusive guard; no retry is required.
86//!
87//! When an exclusive native call may set a process-global error or warning, keep the same
88//! exclusive guard from that call through retrieval of its `errstring` or `warnstring`.
89//!
90//! Do not acquire another access guard while one is already held by the same thread. For this
91//! synchronization boundary to be effective, all access to the bundled native library in a
92//! process must go through [`COOLPROP`]. Constructing [`bindings::CoolProp`] directly bypasses it
93//! and requires equivalent process-wide synchronization from the caller.
94//!
95//! #### License
96//!
97//! <sup>
98//! This project is licensed under
99//! <a href="https://github.com/portyanikhin/rfluids/blob/main/LICENSE">MIT License</a>
100//! </sup>
101
102use std::{
103    ops::Deref,
104    sync::{LazyLock, RwLock, RwLockReadGuard, RwLockWriteGuard},
105};
106
107pub mod bindings;
108
109/// `CoolProp` dynamic library absolute path.
110#[cfg(all(target_os = "linux", target_arch = "aarch64"))]
111pub const COOLPROP_PATH: &str = coolprop_sys_linux_aarch64::COOLPROP_PATH;
112#[cfg(all(target_os = "linux", target_arch = "x86_64"))]
113pub const COOLPROP_PATH: &str = coolprop_sys_linux_x86_64::COOLPROP_PATH;
114#[cfg(all(target_os = "macos", target_arch = "aarch64"))]
115pub const COOLPROP_PATH: &str = coolprop_sys_macos_aarch64::COOLPROP_PATH;
116#[cfg(all(target_os = "macos", target_arch = "x86_64"))]
117pub const COOLPROP_PATH: &str = coolprop_sys_macos_x86_64::COOLPROP_PATH;
118#[cfg(all(target_os = "windows", target_arch = "aarch64"))]
119pub const COOLPROP_PATH: &str = coolprop_sys_windows_aarch64::COOLPROP_PATH;
120#[cfg(all(target_os = "windows", target_arch = "x86_64"))]
121pub const COOLPROP_PATH: &str = coolprop_sys_windows_x86_64::COOLPROP_PATH;
122
123/// Process-wide synchronization boundary for the loaded `CoolProp` dynamic library.
124///
125/// Use [`CoolPropLib::shared_access`] only for native operations known to support concurrent
126/// execution. Use [`CoolPropLib::exclusive_access`] for configuration and debug changes, global
127/// error or warning handling, `REFPROP` operations, `VTPR` construction or reload, tabular
128/// backends, and operations whose concurrency guarantees are unknown. When in doubt, use
129/// exclusive access.
130///
131/// Do not acquire a second access guard while another guard is held by the same thread. Drop the
132/// current guard before changing access modes.
133///
134/// For this synchronization boundary to be effective, all access to the bundled native library in
135/// a process must go through [`COOLPROP`]. Constructing [`bindings::CoolProp`] directly bypasses
136/// this boundary and requires equivalent process-wide synchronization from the caller.
137pub struct CoolPropLib(RwLock<bindings::CoolProp>);
138
139impl CoolPropLib {
140    /// Acquires shared access to the native library.
141    ///
142    /// A shared guard does not make an arbitrary native function or backend reentrant. Use it only
143    /// for operations explicitly known to support concurrent execution, such as calculations on
144    /// independent states backed by `HEOS`, `INCOMP`, `IF97`, `SRK`, `PR`, `PCSAFT`, or an
145    /// already-constructed `VTPR` state.
146    ///
147    /// Some native functions report failure through a sentinel value and store details in the
148    /// process-global `errstring`. Do not release shared access and then treat that string as the
149    /// error from the failed call: another failure may replace it first. To retrieve attributable
150    /// error details:
151    ///
152    /// 1. Release the shared guard.
153    /// 2. Acquire exclusive access.
154    /// 3. Read and discard any stale `errstring` with
155    ///    [`get_global_param_string`](bindings::CoolProp::get_global_param_string), which clears
156    ///    the stored message.
157    /// 4. Repeat the complete native call.
158    /// 5. Read `errstring` with
159    ///    [`get_global_param_string`](bindings::CoolProp::get_global_param_string) before releasing
160    ///    the same exclusive guard.
161    ///
162    /// If error details are not needed, perform only steps 1–3; no retry is required.
163    ///
164    /// Lock poisoning is recovered transparently; access does not panic solely because a previous
165    /// guard holder panicked.
166    ///
167    /// # Examples
168    ///
169    /// ```no_run
170    /// use coolprop_sys::COOLPROP;
171    ///
172    /// let coolprop = COOLPROP.shared_access();
173    /// let critical_temperature = unsafe { coolprop.Props1SI(c"Water".as_ptr(), c"Tcrit".as_ptr()) };
174    /// assert!(critical_temperature.is_finite());
175    /// ```
176    pub fn shared_access(&self) -> SharedAccess<'_> {
177        SharedAccess(self.0.read().unwrap_or_else(|err| err.into_inner()))
178    }
179
180    /// Acquires exclusive access to the native library.
181    ///
182    /// Use this for configuration changes, pending-error retrieval, `REFPROP` calls, `VTPR` state
183    /// construction, tabular backends, and other calls that touch mutable process-global state.
184    /// When a native call may set a process-global error or warning, keep the same exclusive guard
185    /// from that call through retrieval of its `errstring` or `warnstring`.
186    ///
187    /// Lock poisoning is recovered transparently; access does not panic solely because a previous
188    /// guard holder panicked.
189    ///
190    /// # Examples
191    ///
192    /// ```no_run
193    /// use coolprop_sys::COOLPROP;
194    ///
195    /// let coolprop = COOLPROP.exclusive_access();
196    /// unsafe {
197    ///     coolprop.set_debug_level(0);
198    /// }
199    /// ```
200    pub fn exclusive_access(&self) -> ExclusiveAccess<'_> {
201        ExclusiveAccess(self.0.write().unwrap_or_else(|err| err.into_inner()))
202    }
203}
204
205/// Shared access to native operations known to support concurrent execution.
206#[must_use]
207pub struct SharedAccess<'a>(RwLockReadGuard<'a, bindings::CoolProp>);
208
209impl Deref for SharedAccess<'_> {
210    type Target = bindings::CoolProp;
211
212    fn deref(&self) -> &Self::Target {
213        &self.0
214    }
215}
216
217/// Exclusive access to native `CoolProp` calls that must not overlap other calls.
218///
219/// This type intentionally does not implement [`DerefMut`](std::ops::DerefMut): exclusive access
220/// is an execution mode, not permission to replace or mutate the loaded function table.
221#[must_use]
222pub struct ExclusiveAccess<'a>(RwLockWriteGuard<'a, bindings::CoolProp>);
223
224impl Deref for ExclusiveAccess<'_> {
225    type Target = bindings::CoolProp;
226
227    fn deref(&self) -> &Self::Target {
228        &self.0
229    }
230}
231
232/// Global instance of the `CoolProp` dynamic library.
233///
234/// The library is loaded lazily. Before the handle is published, an internal probe initializes
235/// native process-global configuration. This is automatic; callers do not need to perform a
236/// special first native call.
237///
238/// # Panics
239///
240/// Panics on initialization if the `CoolProp` dynamic library cannot be loaded or its
241/// initialization probe does not produce a finite value.
242///
243/// # Safety
244///
245/// Methods exposed by [`bindings::CoolProp`] remain unsafe. Callers must uphold each function's
246/// pointer and lifetime requirements and select the access mode required by the native operation.
247/// Loading and the initialization probe occur once, but synchronization is effective only for
248/// calls made through this handle.
249///
250/// # See Also
251///
252/// - [`CoolPropLib.h` Reference](https://coolprop.org/_static/doxygen/html/_cool_prop_2_cool_prop_lib_8h.html)
253pub static COOLPROP: LazyLock<CoolPropLib> = LazyLock::new(load_coolprop);
254
255fn load_coolprop() -> CoolPropLib {
256    let coolprop = unsafe { bindings::CoolProp::new(COOLPROP_PATH) }
257        .expect("CoolProp dynamic library should load from `COOLPROP_PATH`");
258    let probe = unsafe { coolprop.Props1SI(c"Water".as_ptr(), c"Tcrit".as_ptr()) };
259    assert!(
260        probe.is_finite(),
261        "CoolProp initialization probe `Props1SI(\"Water\", \"Tcrit\")` should return a finite value"
262    );
263    CoolPropLib(RwLock::new(coolprop))
264}
265
266#[cfg(test)]
267mod tests {
268    use std::{sync::TryLockError, thread};
269
270    use static_assertions::assert_not_impl_any;
271
272    use super::*;
273
274    assert_not_impl_any!(ExclusiveAccess<'static>: std::ops::DerefMut);
275
276    fn test_lib() -> CoolPropLib {
277        LazyLock::force(&COOLPROP);
278        let coolprop = unsafe { bindings::CoolProp::new(COOLPROP_PATH) }
279            .expect("CoolProp dynamic library should load from `COOLPROP_PATH`");
280        CoolPropLib(RwLock::new(coolprop))
281    }
282
283    fn shared_access_is_available(lib: &CoolPropLib) -> bool {
284        match lib.0.try_read() {
285            Ok(_) | Err(TryLockError::Poisoned(_)) => true,
286            Err(TryLockError::WouldBlock) => false,
287        }
288    }
289
290    fn exclusive_access_is_available(lib: &CoolPropLib) -> bool {
291        match lib.0.try_write() {
292            Ok(_) | Err(TryLockError::Poisoned(_)) => true,
293            Err(TryLockError::WouldBlock) => false,
294        }
295    }
296
297    #[test]
298    fn access_types_deref_to_coolprop() {
299        // Given
300        let lib = test_lib();
301
302        // When
303        let shared = lib.shared_access();
304        let shared_target = std::ptr::from_ref::<bindings::CoolProp>(&shared);
305        drop(shared);
306        let exclusive = lib.exclusive_access();
307        let exclusive_target = std::ptr::from_ref::<bindings::CoolProp>(&exclusive);
308
309        // Then
310        assert_eq!(shared_target, exclusive_target);
311    }
312
313    #[test]
314    fn poisoned_lock_is_recovered() {
315        // Given
316        let lib = test_lib();
317
318        // When
319        let panic_result = thread::scope(|scope| {
320            scope
321                .spawn(|| {
322                    let _access = lib.exclusive_access();
323                    panic!("poison the test lock");
324                })
325                .join()
326        });
327        let shared = lib.shared_access();
328        let shared_level = unsafe { shared.get_debug_level() };
329        drop(shared);
330        let exclusive = lib.exclusive_access();
331        let exclusive_level = unsafe { exclusive.get_debug_level() };
332
333        // Then
334        assert!(panic_result.is_err());
335        assert!((0..=10).contains(&shared_level));
336        assert!((0..=10).contains(&exclusive_level));
337    }
338
339    #[test]
340    fn shared_access_allows_another_reader_and_blocks_a_writer() {
341        // Given
342        let lib = test_lib();
343        let _shared = lib.shared_access();
344
345        // When
346        let another_reader_is_available = shared_access_is_available(&lib);
347        let writer_is_available = exclusive_access_is_available(&lib);
348
349        // Then
350        assert!(another_reader_is_available);
351        assert!(!writer_is_available);
352    }
353
354    #[test]
355    fn exclusive_access_blocks_other_access() {
356        // Given
357        let lib = test_lib();
358        let _exclusive = lib.exclusive_access();
359
360        // When
361        let reader_is_available = shared_access_is_available(&lib);
362        let writer_is_available = exclusive_access_is_available(&lib);
363
364        // Then
365        assert!(!reader_is_available);
366        assert!(!writer_is_available);
367    }
368
369    #[test]
370    fn unlocked_lib_allows_exclusive_access() {
371        // Given
372        let lib = test_lib();
373
374        // When
375        let writer_is_available = exclusive_access_is_available(&lib);
376
377        // Then
378        assert!(writer_is_available);
379    }
380}