SzEnvironmentCore

Struct SzEnvironmentCore 

Source
pub struct SzEnvironmentCore {
    is_destroyed: Arc<AtomicBool>,
    init_once: Arc<Once>,
    init_error: Arc<Mutex<Option<String>>>,
    config_mgr_init_once: Arc<Once>,
    config_mgr_init_error: Arc<Mutex<Option<String>>>,
    product_init_once: Arc<Once>,
    product_init_error: Arc<Mutex<Option<String>>>,
    module_name: String,
    ini_params: String,
    verbose_logging: bool,
}
Expand description

Core implementation of the SzEnvironment trait.

SzEnvironmentCore manages the lifecycle of the Senzing environment and serves as a factory for obtaining instances of other SDK components. It implements a singleton pattern required by the Senzing native library.

§Complete Lifecycle Example

This example shows the full lifecycle: creation, usage, and cleanup.

use sz_rust_sdk::prelude::*;
use std::sync::Arc;

// Get SDK components
let engine = env.get_engine()?;
let product = env.get_product()?;

// Use the SDK
let version = product.get_version()?;
println!("Senzing version: {}", version);

engine.add_record("TEST", "LIFECYCLE_1",
    r#"{"NAME_FULL": "John Smith"}"#, None)?;

// Drop components first (they borrow the environment)
drop(engine);
drop(product);

// Destroy releases native resources
env.destroy()?;

§Singleton Pattern

The Senzing native library requires a single global instance per process. get_instance() enforces this by storing one Arc reference in a static variable. This means:

  • All get_instance() calls with the same parameters return the same instance
  • The returned Arc always has strong_count() >= 2 (singleton + caller)
  • Calling destroy() removes the singleton reference and cleans up native resources only when you hold the last reference

§Thread Safety

SzEnvironmentCore is thread-safe (Send + Sync). The Arc<SzEnvironmentCore> can be cloned and shared across threads. Each call to get_engine(), etc., returns an independent component instance that can be used in its own thread.

use sz_rust_sdk::prelude::*;

let handles: Vec<_> = (0..4).map(|i| {
    let env = env.clone();
    std::thread::spawn(move || {
        let engine = env.get_engine().unwrap();
        engine.add_record("TEST", &format!("THREAD_{}", i),
            r#"{"NAME_FULL": "Test User"}"#, None)
    })
}).collect();

for h in handles { h.join().unwrap()?; }

Fields§

§is_destroyed: Arc<AtomicBool>§init_once: Arc<Once>

Guards Sz_init() - ensures it runs exactly once and other threads wait

§init_error: Arc<Mutex<Option<String>>>

Stores any error that occurred during Sz_init

§config_mgr_init_once: Arc<Once>

Guards SzConfigMgr_init() - ensures it runs exactly once and other threads wait

§config_mgr_init_error: Arc<Mutex<Option<String>>>

Stores any error that occurred during SzConfigMgr_init

§product_init_once: Arc<Once>

Guards SzProduct_init() - ensures it runs exactly once and other threads wait

§product_init_error: Arc<Mutex<Option<String>>>

Stores any error that occurred during SzProduct_init

§module_name: String§ini_params: String§verbose_logging: bool

Implementations§

Source§

impl SzEnvironmentCore

Source

pub fn new( module_name: &str, ini_params: &str, verbose_logging: bool, ) -> SzResult<Self>

Creates a new SzEnvironment instance

§Arguments
  • module_name - Name of the module for logging purposes
  • ini_params - JSON string containing initialization parameters
  • verbose_logging - Whether to enable verbose logging
Source

pub fn new_default() -> SzResult<Self>

Creates a new SzEnvironment instance with default parameters

Source

pub fn get_instance( module_name: &str, ini_params: &str, verbose_logging: bool, ) -> SzResult<Arc<Self>>

Gets or creates the global singleton SzEnvironmentCore instance

This method ensures that only one SzEnvironmentCore instance exists per process, which is required by the Senzing SDK.

§Arguments
  • module_name - Name of the module for logging purposes
  • ini_params - JSON string containing initialization parameters
  • verbose_logging - Whether to enable verbose logging
§Arc Reference Count Behavior

Important: The returned Arc<SzEnvironmentCore> will always have strong_count() >= 2 due to the singleton pattern:

  • Reference 1: Stored in GLOBAL_ENVIRONMENT (the singleton storage)
  • Reference 2+: Returned to the caller (and any clones they make)

This is by design and is NOT a memory leak. The singleton reference ensures that subsequent calls to get_instance() return the same instance.

use sz_rust_sdk::prelude::*;
use std::sync::Arc;

assert!(Arc::strong_count(&env) >= 2); // Expected: singleton + caller

let env2 = SzEnvironmentCore::get_existing_instance()?;
assert!(Arc::ptr_eq(&env, &env2)); // Same instance
assert!(Arc::strong_count(&env) >= 3); // singleton + 2 callers
§Cleanup

To properly release resources, use destroy() which removes the singleton reference and calls native cleanup when you hold the last reference:

use sz_rust_sdk::prelude::*;

let env2 = SzEnvironmentCore::get_existing_instance()?;
// Drop all other references first
drop(env2);
// Then destroy - this removes singleton ref and cleans up native resources
env.destroy()?;
Source

pub fn get_existing_instance() -> SzResult<Arc<Self>>

Gets the existing global singleton SzEnvironmentCore instance

This method returns the existing singleton instance without creating a new one. It will return an error if no instance has been created yet.

§Returns

Returns the existing singleton instance or an error if none exists.

Source

pub fn try_get_instance() -> Option<Arc<Self>>

Gets the global singleton instance if it exists

Returns None if no instance has been created yet.

Source

pub fn destroy(self: Arc<Self>) -> SzResult<()>

Destroys the environment, consuming the Arc and releasing all native resources.

This method uses Rust’s ownership semantics to ensure safe cleanup:

  • Only succeeds if the caller holds the sole reference to the environment
  • If other references exist (e.g., other threads still using the environment), returns an error and the environment remains valid
§Ownership Requirements

The Senzing native library is a global resource. Destroying the environment while other code still holds references would cause undefined behavior. This method enforces that you can only destroy when you’re the sole owner.

§Example
use sz_rust_sdk::prelude::*;

// ... use env ...

// When done, destroy (only works if this is the only reference)
env.destroy()?;
§Calling destroy() from a Drop Implementation

Because destroy() takes Arc<Self> by value, calling it from a Drop impl requires moving the Arc out of your struct. Use Option<Arc<...>> with .take() — this is exactly what SenzingGuard does internally.

use sz_rust_sdk::prelude::*;
use std::sync::Arc;

// CORRECT: Use Option<Arc<...>> + take()
struct MyWrapper {
    env: Option<Arc<SzEnvironmentCore>>,
}

impl Drop for MyWrapper {
    fn drop(&mut self) {
        if let Some(env) = self.env.take() {
            let _ = env.destroy();
        }
    }
}

let wrapper = MyWrapper { env: Some(env) };
drop(wrapper); // Cleanup happens via Drop
use sz_rust_sdk::prelude::*;
use std::sync::Arc;

// WRONG: ptr::read causes double-free / heap corruption
struct MyWrapper {
    env: Arc<SzEnvironmentCore>,  // Not wrapped in Option!
}

impl Drop for MyWrapper {
    fn drop(&mut self) {
        // BUG: ptr::read creates a second Arc without incrementing the
        // reference count. After destroy() frees the inner data, the
        // compiler also drops self.env -> double-free -> heap corruption.
        let env = unsafe { std::ptr::read(&self.env) };
        let _ = env.destroy();
        // <- compiler drops self.env here (use-after-free!)
    }
}

Note: Using ManuallyDrop instead of ptr::read avoids the double-free but is unnecessarily complex. Prefer Option::take() or use SenzingGuard directly — it handles all of this correctly.

§Errors

Returns SzError::Unrecoverable if:

  • Other references to the environment still exist
  • The environment was already destroyed
Source

pub fn get_ini_params(&self) -> &str

Get the initialization parameters used by this environment

Source

pub fn get_verbose_logging(&self) -> bool

Get the verbose logging setting used by this environment

Source

fn ensure_initialized(&self) -> SzResult<()>

Ensures Sz_init has been called - should be called before any engine operations

This method is thread-safe: the first thread to call this will run Sz_init(), and all other threads will block until initialization is complete.

Source

fn ensure_config_mgr_initialized(&self) -> SzResult<()>

Ensures SzConfigMgr_init has been called - should be called before any config manager operations

This method is thread-safe: the first thread to call this will run SzConfigMgr_init(), and all other threads will block until initialization is complete.

Source

fn ensure_product_initialized(&self) -> SzResult<()>

Ensures SzProduct_init has been called - should be called before any product operations

This method is thread-safe: the first thread to call this will run SzProduct_init(), and all other threads will block until initialization is complete.

Trait Implementations§

Source§

impl Drop for SzEnvironmentCore

§Drop Behavior - Intentionally Does NOT Clean Up Native Resources

The Drop implementation for SzEnvironmentCore is intentionally a no-op that only marks the environment as destroyed without releasing native Senzing resources. This design is deliberate for several important reasons:

§Why Drop Doesn’t Clean Up
  1. Singleton Pattern Complexity: Due to the global singleton pattern, multiple Arc references exist (one in GLOBAL_ENVIRONMENT, one or more held by callers). The Drop trait cannot detect if this is the “last” reference being dropped.

  2. Native Library Safety: The Senzing native library has specific destruction ordering requirements. Calling Sz_destroy() while other threads might still be using the library can cause undefined behavior.

  3. Controlled Shutdown: Users need explicit control over when native resources are released, especially in applications that may reinitialize with different configurations.

§Proper Cleanup Pattern

Recommended: Use SenzingGuard for automatic RAII cleanup:

use sz_rust_sdk::prelude::*;

// Explicit destroy (ownership-based)
let engine = env.get_engine()?;
// ... use engine ...
drop(engine);
env.destroy()?;  // Explicitly release native resources

If you need to call destroy() from your own Drop implementation, see the safety notes on destroy() — you must store the Arc in an Option and use .take() to avoid double-free bugs.

§What This Implementation Does
  • Marks the environment as “destroyed” to prevent further API calls
  • Does NOT call Sz_destroy() or other native cleanup functions
  • Allows garbage collection of Rust-side resources
Source§

fn drop(&mut self)

Executes the destructor for this type. Read more
Source§

impl SzEnvironment for SzEnvironmentCore

Source§

fn is_destroyed(&self) -> bool

Checks if the environment has been destroyed. Read more
Source§

fn reinitialize(&self, config_id: ConfigId) -> SzResult<()>

Reinitializes the environment with a different configuration. Read more
Source§

fn get_active_config_id(&self) -> SzResult<ConfigId>

Gets the currently active configuration ID. Read more
Source§

fn get_product(&self) -> SzResult<Box<dyn SzProduct>>

Gets the product interface for version and license information. Read more
Source§

fn get_engine(&self) -> SzResult<Box<dyn SzEngine>>

Gets the engine interface for entity resolution operations. Read more
Source§

fn get_config_manager(&self) -> SzResult<Box<dyn SzConfigManager>>

Gets the configuration manager interface. Read more
Source§

fn get_diagnostic(&self) -> SzResult<Box<dyn SzDiagnostic>>

Gets the diagnostic interface for system monitoring. Read more

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.