Skip to main content

PropertyObject

Struct PropertyObject 

Source
pub struct PropertyObject { /* private fields */ }
Expand description

Safe wrapper for TestStand™ PropertyObject (IPropertyObject).

Implementations§

Source§

impl PropertyObject

Source

pub fn exists(&self, lookup_string: &str, options: i32) -> Result<bool, Error>

Checks if a property exists by lookup path (PropertyObject.Exists).

§Errors

Error if the COM call fails or returns an unexpected type.

Examples found in repository?
examples/variables_manage.rs (line 28)
23fn set_string(
24    container: &PropertyObject,
25    name: &str,
26    value: &str,
27) -> Result<(), rs_teststand::Error> {
28    if !container.exists(name, 0)? {
29        container.new_sub_property(name, PropValType::String, false, "", INSERT_IF_MISSING)?;
30    }
31    container.set_val_string(name, 0, value)
32}
33
34/// Creates a variable, retypes it, clones it, then removes both.
35///
36/// The type is fixed at creation, so each "retype" is a delete followed by a
37/// fresh create, the same thing the sequence editor does behind the scenes.
38fn temporary_variable_lifecycle(container: &PropertyObject) -> Result<(), rs_teststand::Error> {
39    let name = "TempScratch";
40    let clone_name = "TempScratchCopy";
41    for stale in [name, clone_name] {
42        if container.exists(stale, 0)? {
43            container.delete_sub_property(stale, 0)?;
44        }
45    }
46
47    container.new_sub_property(name, PropValType::String, false, "", INSERT_IF_MISSING)?;
48    container.set_val_string(name, 0, "scratch")?;
49    println!(
50        "  created {name} (String) = '{}'",
51        container.get_val_string(name, 0)?
52    );
53
54    container.delete_sub_property(name, 0)?;
55    container.new_sub_property(name, PropValType::Number, false, "", INSERT_IF_MISSING)?;
56    container.set_val_number(name, 0, 42.0)?;
57    println!(
58        "  retyped {name} -> Number = {}",
59        container.get_val_number(name, 0)?
60    );
61
62    // clone copies value and type; set_property_object attaches it under a new name.
63    let copy = container.clone_property(name, 0)?;
64    container.set_property_object(clone_name, INSERT_IF_MISSING, &copy)?;
65    println!(
66        "  cloned  {name} -> {clone_name} = {}",
67        container.get_val_number(clone_name, 0)?
68    );
69
70    container.delete_sub_property(clone_name, 0)?;
71    container.delete_sub_property(name, 0)?;
72    println!(
73        "  removed both: {name} exists={}, {clone_name} exists={}",
74        container.exists(name, 0)?,
75        container.exists(clone_name, 0)?
76    );
77    Ok(())
78}
79
80/// Opens a sequence file if one was named on the command line.
81///
82/// Locals, parameters and file globals all need a real file; without one the
83/// example still demonstrates station globals.
84fn open_sequence_file(engine: &Engine) -> Result<Option<SequenceFile>, rs_teststand::Error> {
85    let Some(path) = std::env::args().nth(1) else {
86        return Ok(None);
87    };
88    let file = engine.get_sequence_file_ex(
89        &path,
90        GetSeqFileOptions::DO_NOT_RUN_LOAD_CALLBACK,
91        ConflictHandler::Error,
92    )?;
93    Ok(Some(file))
94}
95
96/// Everything that lives on the station rather than in a file.
97///
98/// Separated because it is the one scope that outlives the process, so it is
99/// the one worth reading on its own.
100fn show_station_globals(engine: &Engine) -> Result<(), rs_teststand::Error> {
101    // Station globals: shared by every sequence file, and written to disk.
102    let station_globals = engine.globals()?;
103    if !station_globals.exists("StationInfo", 0)? {
104        station_globals.new_sub_property(
105            "StationInfo",
106            PropValType::Container,
107            false,
108            "",
109            INSERT_IF_MISSING,
110        )?;
111    }
112    let station_info = station_globals.get_property_object("StationInfo", 0)?;
113
114    // A container holds mixed types, so one station record can carry the name,
115    // a count and a flag without three separate globals.
116    set_string(&station_info, "StationName", "STATION_01")?;
117    station_info.set_val_number("CalibrationIntervalDays", INSERT_IF_MISSING, 90.0)?;
118    station_info.set_val_bool("FixtureInstalled", INSERT_IF_MISSING, true)?;
119
120    // A 64-bit count. `SetValNumber` stores a double, which starts losing whole
121    // numbers past 2^53, so anything that counts for the life of a station wants
122    // the integer path instead.
123    station_info.set_val_integer64("UnitsTestedTotal", INSERT_IF_MISSING, 9_007_199_254_740_993)?;
124
125    // A nested container, because station data is rarely one level deep.
126    if !station_info.exists("LastCalibration", 0)? {
127        station_info.new_sub_property(
128            "LastCalibration",
129            PropValType::Container,
130            false,
131            "",
132            INSERT_IF_MISSING,
133        )?;
134    }
135    let calibration = station_info.get_property_object("LastCalibration", 0)?;
136    set_string(&calibration, "Technician", "R. Alvarez")?;
137    set_string(&calibration, "Date", "2026-06-14")?;
138
139    println!("StationGlobals.StationInfo:");
140    println!(
141        "  StationName             = '{}'",
142        station_info.get_val_string("StationName", 0)?
143    );
144    println!(
145        "  CalibrationIntervalDays = {}",
146        station_info.get_val_number("CalibrationIntervalDays", 0)?
147    );
148    println!(
149        "  FixtureInstalled        = {}",
150        station_info.get_val_bool("FixtureInstalled", 0)?
151    );
152    println!(
153        "  UnitsTestedTotal        = {}",
154        station_info.get_val_integer64("UnitsTestedTotal", 0)?
155    );
156    println!(
157        "  LastCalibration.Technician = '{}' on {}",
158        calibration.get_val_string("Technician", 0)?,
159        calibration.get_val_string("Date", 0)?
160    );
161
162    // Walk the container rather than naming each field, which is what a host
163    // does when it does not know the shape in advance.
164    println!(
165        "  walked, {} field(s):",
166        station_info.get_num_sub_properties("")?
167    );
168    for index in 0..station_info.get_num_sub_properties("")? {
169        println!(
170            "    {}",
171            station_info.get_nth_sub_property_name("", index, 0)?
172        );
173    }
174
175    // Station globals live in memory until this is called. Without it the values
176    // above are gone when the engine goes away, which is the difference between
177    // a station global and a file global.
178    // `false` means do not prompt if another process changed the file first.
179    // An example must never raise a dialog, and neither must a headless host.
180    engine.commit_globals_to_disk(false)?;
181    println!("  committed to disk");
182
183    Ok(())
184}
More examples
Hide additional examples
examples/sequence_build.rs (line 53)
46fn describe(sequence: &Sequence) -> Result<(), rs_teststand::Error> {
47    let count = sequence.get_num_steps(StepGroup::Main)?;
48    println!("{} holds {count} step(s) in Main:", sequence.name()?);
49    for index in 0..count {
50        let step = sequence.get_step(index, StepGroup::Main)?;
51        let properties = step.as_property_object()?;
52        println!("  [{index}] {}", step.name()?);
53        if properties.exists("Limits.Low", NO_OPTIONS)? {
54            println!(
55                "      limits: low={}, high={}",
56                properties.get_val_number("Limits.Low", NO_OPTIONS)?,
57                properties.get_val_number("Limits.High", NO_OPTIONS)?
58            );
59        }
60        let precondition = step.precondition()?;
61        if !precondition.is_empty() {
62            println!("      runs when: {precondition}");
63        }
64    }
65    Ok(())
66}
examples/execution_run_subsequence.rs (line 120)
108fn run(engine: &Engine, sequence_file: &SequenceFile, entry_point: &str) -> Result<(), Error> {
109    let execution = engine.new_execution(sequence_file, entry_point, None, false, 0)?;
110    println!("\nRunning {entry_point}...");
111
112    if !wait_for_end(engine, RUN_DEADLINE)? {
113        println!("  did not finish within {RUN_DEADLINE:?}; terminating");
114        execution.terminate()?;
115        return Ok(());
116    }
117    println!("  status: {}", execution.result_status()?);
118
119    let results = execution.result_object()?;
120    if !results.exists("ResultList", none())? {
121        println!("  no results recorded");
122        return Ok(());
123    }
124    let result_list = results.get_property_object("ResultList", none())?;
125    for index in 0..result_list.get_num_elements()? {
126        let entry = result_list.get_property_object_by_offset(index, none())?;
127        println!(
128            "  {}: {}",
129            entry
130                .get_val_string("TS.StepName", none())
131                .unwrap_or_else(|_| "<unnamed>".to_owned()),
132            entry
133                .get_val_string("Status", none())
134                .unwrap_or_else(|_| "<no status>".to_owned())
135        );
136    }
137    Ok(())
138}
examples/execution_run_test_headless.rs (line 115)
114fn report(results: &rs_teststand::PropertyObject) -> Result<(), Error> {
115    if !results.exists("ResultList", none())? {
116        println!("No results recorded.");
117        return Ok(());
118    }
119    let result_list = results.get_property_object("ResultList", none())?;
120    let count = result_list.get_num_elements()?;
121    println!(
122        "\n{count} of {} defined test(s) recorded a result:",
123        TESTS.len()
124    );
125    if count < i32::try_from(TESTS.len()).unwrap_or(i32::MAX) {
126        println!("  (a failure ended the run before the rest could execute)");
127    }
128
129    for index in 0..count {
130        let entry = result_list.get_property_object_by_offset(index, none())?;
131        let name = entry
132            .get_val_string("TS.StepName", none())
133            .unwrap_or_else(|_| "<unnamed>".to_owned());
134        let status = entry
135            .get_val_string("Status", none())
136            .unwrap_or_else(|_| "<no status>".to_owned());
137        // Only test steps record a measurement; an Action has no Numeric.
138        match entry.get_val_number("Numeric", none()) {
139            Ok(measured) => println!("  {name}: {status} (measured {measured})"),
140            Err(_) => println!("  {name}: {status}"),
141        }
142    }
143    Ok(())
144}
Source

pub fn get_val_string( &self, lookup_string: &str, options: i32, ) -> Result<String, Error>

Reads a string property by lookup path (PropertyObject.GetValString).

§Errors

Error if the COM call fails or returns an unexpected type.

Examples found in repository?
examples/execution_run_subsequence.rs (line 130)
108fn run(engine: &Engine, sequence_file: &SequenceFile, entry_point: &str) -> Result<(), Error> {
109    let execution = engine.new_execution(sequence_file, entry_point, None, false, 0)?;
110    println!("\nRunning {entry_point}...");
111
112    if !wait_for_end(engine, RUN_DEADLINE)? {
113        println!("  did not finish within {RUN_DEADLINE:?}; terminating");
114        execution.terminate()?;
115        return Ok(());
116    }
117    println!("  status: {}", execution.result_status()?);
118
119    let results = execution.result_object()?;
120    if !results.exists("ResultList", none())? {
121        println!("  no results recorded");
122        return Ok(());
123    }
124    let result_list = results.get_property_object("ResultList", none())?;
125    for index in 0..result_list.get_num_elements()? {
126        let entry = result_list.get_property_object_by_offset(index, none())?;
127        println!(
128            "  {}: {}",
129            entry
130                .get_val_string("TS.StepName", none())
131                .unwrap_or_else(|_| "<unnamed>".to_owned()),
132            entry
133                .get_val_string("Status", none())
134                .unwrap_or_else(|_| "<no status>".to_owned())
135        );
136    }
137    Ok(())
138}
139
140fn main() -> Result<(), Box<dyn std::error::Error>> {
141    let engine = Engine::new()?;
142    engine.set_ui_message_polling_enabled(true)?;
143
144    let sequence_file = build(&engine)?;
145    println!("File holds {} sequence(s).", sequence_file.num_sequences()?);
146
147    // The conventional entry point.
148    run(&engine, &sequence_file, "MainSequence")?;
149
150    // A subsequence run on its own has no caller, so nothing supplies its
151    // parameters, they keep whatever default the sequence carries. Setting
152    // that default is therefore how a direct run is given its input.
153    let diagnostics = sequence_file.get_sequence_by_name(SUBSEQUENCE)?;
154    diagnostics
155        .parameters()?
156        .set_val_string(PARAMETER, none(), "FIXTURE-07")?;
157    println!(
158        "\n{SUBSEQUENCE}.{PARAMETER} default is now {:?}",
159        diagnostics
160            .parameters()?
161            .get_val_string(PARAMETER, none())?
162    );
163
164    run(&engine, &sequence_file, SUBSEQUENCE)?;
165
166    engine.release_sequence_file_ex(sequence_file, none())?;
167    Ok(())
168}
More examples
Hide additional examples
examples/execution_run_test_headless.rs (line 132)
114fn report(results: &rs_teststand::PropertyObject) -> Result<(), Error> {
115    if !results.exists("ResultList", none())? {
116        println!("No results recorded.");
117        return Ok(());
118    }
119    let result_list = results.get_property_object("ResultList", none())?;
120    let count = result_list.get_num_elements()?;
121    println!(
122        "\n{count} of {} defined test(s) recorded a result:",
123        TESTS.len()
124    );
125    if count < i32::try_from(TESTS.len()).unwrap_or(i32::MAX) {
126        println!("  (a failure ended the run before the rest could execute)");
127    }
128
129    for index in 0..count {
130        let entry = result_list.get_property_object_by_offset(index, none())?;
131        let name = entry
132            .get_val_string("TS.StepName", none())
133            .unwrap_or_else(|_| "<unnamed>".to_owned());
134        let status = entry
135            .get_val_string("Status", none())
136            .unwrap_or_else(|_| "<no status>".to_owned());
137        // Only test steps record a measurement; an Action has no Numeric.
138        match entry.get_val_number("Numeric", none()) {
139            Ok(measured) => println!("  {name}: {status} (measured {measured})"),
140            Err(_) => println!("  {name}: {status}"),
141        }
142    }
143    Ok(())
144}
examples/variables_manage.rs (line 51)
38fn temporary_variable_lifecycle(container: &PropertyObject) -> Result<(), rs_teststand::Error> {
39    let name = "TempScratch";
40    let clone_name = "TempScratchCopy";
41    for stale in [name, clone_name] {
42        if container.exists(stale, 0)? {
43            container.delete_sub_property(stale, 0)?;
44        }
45    }
46
47    container.new_sub_property(name, PropValType::String, false, "", INSERT_IF_MISSING)?;
48    container.set_val_string(name, 0, "scratch")?;
49    println!(
50        "  created {name} (String) = '{}'",
51        container.get_val_string(name, 0)?
52    );
53
54    container.delete_sub_property(name, 0)?;
55    container.new_sub_property(name, PropValType::Number, false, "", INSERT_IF_MISSING)?;
56    container.set_val_number(name, 0, 42.0)?;
57    println!(
58        "  retyped {name} -> Number = {}",
59        container.get_val_number(name, 0)?
60    );
61
62    // clone copies value and type; set_property_object attaches it under a new name.
63    let copy = container.clone_property(name, 0)?;
64    container.set_property_object(clone_name, INSERT_IF_MISSING, &copy)?;
65    println!(
66        "  cloned  {name} -> {clone_name} = {}",
67        container.get_val_number(clone_name, 0)?
68    );
69
70    container.delete_sub_property(clone_name, 0)?;
71    container.delete_sub_property(name, 0)?;
72    println!(
73        "  removed both: {name} exists={}, {clone_name} exists={}",
74        container.exists(name, 0)?,
75        container.exists(clone_name, 0)?
76    );
77    Ok(())
78}
79
80/// Opens a sequence file if one was named on the command line.
81///
82/// Locals, parameters and file globals all need a real file; without one the
83/// example still demonstrates station globals.
84fn open_sequence_file(engine: &Engine) -> Result<Option<SequenceFile>, rs_teststand::Error> {
85    let Some(path) = std::env::args().nth(1) else {
86        return Ok(None);
87    };
88    let file = engine.get_sequence_file_ex(
89        &path,
90        GetSeqFileOptions::DO_NOT_RUN_LOAD_CALLBACK,
91        ConflictHandler::Error,
92    )?;
93    Ok(Some(file))
94}
95
96/// Everything that lives on the station rather than in a file.
97///
98/// Separated because it is the one scope that outlives the process, so it is
99/// the one worth reading on its own.
100fn show_station_globals(engine: &Engine) -> Result<(), rs_teststand::Error> {
101    // Station globals: shared by every sequence file, and written to disk.
102    let station_globals = engine.globals()?;
103    if !station_globals.exists("StationInfo", 0)? {
104        station_globals.new_sub_property(
105            "StationInfo",
106            PropValType::Container,
107            false,
108            "",
109            INSERT_IF_MISSING,
110        )?;
111    }
112    let station_info = station_globals.get_property_object("StationInfo", 0)?;
113
114    // A container holds mixed types, so one station record can carry the name,
115    // a count and a flag without three separate globals.
116    set_string(&station_info, "StationName", "STATION_01")?;
117    station_info.set_val_number("CalibrationIntervalDays", INSERT_IF_MISSING, 90.0)?;
118    station_info.set_val_bool("FixtureInstalled", INSERT_IF_MISSING, true)?;
119
120    // A 64-bit count. `SetValNumber` stores a double, which starts losing whole
121    // numbers past 2^53, so anything that counts for the life of a station wants
122    // the integer path instead.
123    station_info.set_val_integer64("UnitsTestedTotal", INSERT_IF_MISSING, 9_007_199_254_740_993)?;
124
125    // A nested container, because station data is rarely one level deep.
126    if !station_info.exists("LastCalibration", 0)? {
127        station_info.new_sub_property(
128            "LastCalibration",
129            PropValType::Container,
130            false,
131            "",
132            INSERT_IF_MISSING,
133        )?;
134    }
135    let calibration = station_info.get_property_object("LastCalibration", 0)?;
136    set_string(&calibration, "Technician", "R. Alvarez")?;
137    set_string(&calibration, "Date", "2026-06-14")?;
138
139    println!("StationGlobals.StationInfo:");
140    println!(
141        "  StationName             = '{}'",
142        station_info.get_val_string("StationName", 0)?
143    );
144    println!(
145        "  CalibrationIntervalDays = {}",
146        station_info.get_val_number("CalibrationIntervalDays", 0)?
147    );
148    println!(
149        "  FixtureInstalled        = {}",
150        station_info.get_val_bool("FixtureInstalled", 0)?
151    );
152    println!(
153        "  UnitsTestedTotal        = {}",
154        station_info.get_val_integer64("UnitsTestedTotal", 0)?
155    );
156    println!(
157        "  LastCalibration.Technician = '{}' on {}",
158        calibration.get_val_string("Technician", 0)?,
159        calibration.get_val_string("Date", 0)?
160    );
161
162    // Walk the container rather than naming each field, which is what a host
163    // does when it does not know the shape in advance.
164    println!(
165        "  walked, {} field(s):",
166        station_info.get_num_sub_properties("")?
167    );
168    for index in 0..station_info.get_num_sub_properties("")? {
169        println!(
170            "    {}",
171            station_info.get_nth_sub_property_name("", index, 0)?
172        );
173    }
174
175    // Station globals live in memory until this is called. Without it the values
176    // above are gone when the engine goes away, which is the difference between
177    // a station global and a file global.
178    // `false` means do not prompt if another process changed the file first.
179    // An example must never raise a dialog, and neither must a headless host.
180    engine.commit_globals_to_disk(false)?;
181    println!("  committed to disk");
182
183    Ok(())
184}
185
186fn main() -> Result<(), rs_teststand::Error> {
187    let engine = Engine::new()?;
188
189    show_station_globals(&engine)?;
190
191    // The other three scopes need a sequence file.
192    if let Some(sequence_file) = open_sequence_file(&engine)? {
193        // File globals: shared by every sequence in this file. These are the
194        // defaults stored in the file; a running execution gets its own copy.
195        let file_globals = sequence_file.file_globals_default_values()?;
196        set_string(&file_globals, "BatchID", "BATCH-2026-Q2-001")?;
197        println!(
198            "FileGlobals.BatchID                   = '{}'",
199            file_globals.get_val_string("BatchID", 0)?
200        );
201
202        let main_sequence = sequence_file.get_sequence_by_name("MainSequence")?;
203
204        // Locals: private to one call of this sequence.
205        let locals = main_sequence.locals()?;
206        set_string(&locals, "OperatorName", "Alice")?;
207        println!(
208            "MainSequence.Locals.OperatorName      = '{}'",
209            locals.get_val_string("OperatorName", 0)?
210        );
211
212        // Parameters: supplied by whoever calls this sequence.
213        let parameters = main_sequence.parameters()?;
214        set_string(&parameters, "DUTSerial", "SN-000000")?;
215        println!(
216            "MainSequence.Parameters.DUTSerial     = '{}'",
217            parameters.get_val_string("DUTSerial", 0)?
218        );
219
220        println!("\nTemporary variable lifecycle (Locals.TempScratch):");
221        temporary_variable_lifecycle(&locals)?;
222
223        // Nothing is saved: the file is left exactly as it was found.
224        engine.release_sequence_file_ex(sequence_file, 0)?;
225    } else {
226        println!("\n(pass a .seq path to also demonstrate locals, parameters and file globals)");
227        println!("\nTemporary variable lifecycle (StationGlobals.TempScratch):");
228        temporary_variable_lifecycle(&engine.globals()?)?;
229    }
230
231    engine.commit_globals_to_disk(false)?;
232    println!("\nStation globals committed to disk.");
233    Ok(())
234}
examples/template_manage_complex.rs (line 166)
131fn apply_templates(
132    sequence_file: &SequenceFile,
133    step: &PropertyObject,
134    sequence: &PropertyObject,
135    variable: &PropertyObject,
136) -> Result<(), Error> {
137    let main_sequence = sequence_file.get_sequence_by_name(MAIN_SEQUENCE)?;
138
139    // Inserting returns the copy on the Step interface, so it can be renamed
140    // and re-identified straight away. Without a new step ID the copy would go
141    // on claiming the prototype's identity.
142    let inserted_step = main_sequence.insert_step_from_template(step, 0, StepGroup::Main)?;
143    inserted_step.set_name("Step From Template")?;
144    inserted_step.create_new_unique_step_id()?;
145    println!("  step:     {}", inserted_step.name()?);
146
147    let inserted_sequence = sequence_file.insert_sequence_from_template(sequence)?;
148    inserted_sequence.create_new_unique_step_ids()?;
149    println!(
150        "  sequence: {} ({} step(s))",
151        inserted_sequence.name()?,
152        inserted_sequence.get_num_steps(StepGroup::Main)?
153    );
154
155    // A variable is the easy case: Locals is a property tree, so a clone drops
156    // straight in under the template's own name.
157    let locals = main_sequence.locals()?;
158    locals.set_property_object(
159        &variable.name()?,
160        PropertyOptions::INSERT_IF_MISSING.bits(),
161        &variable.clone_property("", PropertyOptions::NONE.bits())?,
162    )?;
163    println!(
164        "  variable: {} = {:?}",
165        variable.name()?,
166        locals.get_val_string(&variable.name()?, PropertyOptions::NONE.bits())?
167    );
168
169    // A file that does not believe it changed will not write anything.
170    sequence_file
171        .as_property_object_file()?
172        .inc_change_count()?;
173    Ok(())
174}
examples/step_insert_from_template.rs (line 101)
65fn main() -> Result<(), Box<dyn std::error::Error>> {
66    let engine = Engine::new()?;
67    describe_templates(&engine)?;
68
69    let prototype = build_prototype(&engine)?;
70    println!(
71        "Prototype: {} via {:?}, run mode {:?}",
72        prototype.name()?,
73        prototype.adapter_key_name()?,
74        prototype.run_mode()?
75    );
76
77    let sequence_file = engine.new_sequence_file()?;
78    let main_sequence = sequence_file.get_sequence_by_name("MainSequence")?;
79    let template = prototype.as_property_object()?;
80
81    for copy_number in 1..=COPIES {
82        let index = main_sequence.get_num_steps(StepGroup::Main)?;
83        let inserted =
84            main_sequence.insert_step_from_template(&template, index, StepGroup::Main)?;
85        inserted.set_name(&format!("Measure {copy_number} (from template)"))?;
86        // Each copy needs an identity of its own; the clone brought the
87        // prototype's.
88        inserted.create_new_unique_step_id()?;
89    }
90
91    println!(
92        "\nMainSequence now holds {} step(s):",
93        main_sequence.get_num_steps(StepGroup::Main)?
94    );
95    for index in 0..main_sequence.get_num_steps(StepGroup::Main)? {
96        let step = main_sequence.get_step(index, StepGroup::Main)?;
97        println!(
98            "  [{index}] {} -> {}",
99            step.name()?,
100            step.as_property_object()?
101                .get_val_string(VI_PATH, PropertyOptions::NONE.bits())?
102        );
103    }
104
105    // The prototype is untouched and can go on producing copies.
106    println!("\nPrototype still named: {}", prototype.name()?);
107
108    let path = std::env::temp_dir().join("rs_teststand_from_template.seq");
109    let path = path.to_string_lossy().into_owned();
110    sequence_file.save(&path)?;
111    println!("Saved to {path}");
112    Ok(())
113}
examples/step_insert.rs (line 127)
73fn main() -> Result<(), Box<dyn std::error::Error>> {
74    let engine = Engine::new()?;
75
76    // A file to insert into. Built here so the example depends on nothing that
77    // has to exist on the station first.
78    let sequence_file = engine.new_sequence_file()?;
79    let subsequence = engine.new_sequence()?;
80    subsequence.set_name("CustomSubsequence")?;
81    let existing = engine.new_step(AdapterKeyName::NoneAdapter.as_str(), "Action")?;
82    existing.set_name(TARGET_STEP)?;
83    subsequence.insert_step(&existing, 0, StepGroup::Main)?;
84    sequence_file.insert_sequence(&subsequence)?;
85
86    // Where to insert: in front of the target, or at the end if it is absent.
87    let insert_at = if let Some(index) = index_of(&subsequence, TARGET_STEP, StepGroup::Main)? {
88        println!("Found {TARGET_STEP} at index {index}; inserting in front of it.");
89        index
90    } else {
91        let end = subsequence.get_num_steps(StepGroup::Main)?;
92        println!("{TARGET_STEP} not found; appending at index {end}.");
93        end
94    };
95
96    for (offset, (name, vi, project, mode)) in [
97        (
98            "Measure Voltage (VI)",
99            r"instruments.lvlibp\measure_voltage.vi",
100            "",
101            RunMode::Normal,
102        ),
103        (
104            "Measure Current (VI)",
105            r"instruments.lvlibp\measure_current.vi",
106            r"instruments.lvproj",
107            RunMode::Skip,
108        ),
109    ]
110    .into_iter()
111    .enumerate()
112    {
113        let step = vi_call_step(&engine, name, vi, project, mode)?;
114        let at = insert_at + i32::try_from(offset).unwrap_or(0);
115        subsequence.insert_step(&step, at, StepGroup::Main)?;
116    }
117
118    println!(
119        "\n{} now holds {} step(s):",
120        subsequence.name()?,
121        subsequence.get_num_steps(StepGroup::Main)?
122    );
123    for index in 0..subsequence.get_num_steps(StepGroup::Main)? {
124        let step = subsequence.get_step(index, StepGroup::Main)?;
125        let properties = step.as_property_object()?;
126        let vi = properties
127            .get_val_string(VI_PATH, none())
128            .unwrap_or_else(|_| "<no VI>".to_owned());
129        println!(
130            "  [{index}] {} - {:?}, run mode {:?}, records {:?}",
131            step.name()?,
132            step.adapter_key_name()?,
133            step.run_mode()?,
134            step.result_recording_option()?
135        );
136        if vi != "<no VI>" {
137            println!("        calls {vi}");
138        }
139    }
140
141    let path = std::env::temp_dir().join("rs_teststand_step_insert.seq");
142    let path = path.to_string_lossy().into_owned();
143    sequence_file.save(&path)?;
144    println!("\nSaved to {path}");
145    Ok(())
146}
Source

pub fn set_val_string( &self, lookup_string: &str, options: i32, value: &str, ) -> Result<(), Error>

Writes a string property by lookup path (PropertyObject.SetValString).

§Errors

Error if the COM call fails.

Examples found in repository?
examples/variables_manage.rs (line 31)
23fn set_string(
24    container: &PropertyObject,
25    name: &str,
26    value: &str,
27) -> Result<(), rs_teststand::Error> {
28    if !container.exists(name, 0)? {
29        container.new_sub_property(name, PropValType::String, false, "", INSERT_IF_MISSING)?;
30    }
31    container.set_val_string(name, 0, value)
32}
33
34/// Creates a variable, retypes it, clones it, then removes both.
35///
36/// The type is fixed at creation, so each "retype" is a delete followed by a
37/// fresh create, the same thing the sequence editor does behind the scenes.
38fn temporary_variable_lifecycle(container: &PropertyObject) -> Result<(), rs_teststand::Error> {
39    let name = "TempScratch";
40    let clone_name = "TempScratchCopy";
41    for stale in [name, clone_name] {
42        if container.exists(stale, 0)? {
43            container.delete_sub_property(stale, 0)?;
44        }
45    }
46
47    container.new_sub_property(name, PropValType::String, false, "", INSERT_IF_MISSING)?;
48    container.set_val_string(name, 0, "scratch")?;
49    println!(
50        "  created {name} (String) = '{}'",
51        container.get_val_string(name, 0)?
52    );
53
54    container.delete_sub_property(name, 0)?;
55    container.new_sub_property(name, PropValType::Number, false, "", INSERT_IF_MISSING)?;
56    container.set_val_number(name, 0, 42.0)?;
57    println!(
58        "  retyped {name} -> Number = {}",
59        container.get_val_number(name, 0)?
60    );
61
62    // clone copies value and type; set_property_object attaches it under a new name.
63    let copy = container.clone_property(name, 0)?;
64    container.set_property_object(clone_name, INSERT_IF_MISSING, &copy)?;
65    println!(
66        "  cloned  {name} -> {clone_name} = {}",
67        container.get_val_number(clone_name, 0)?
68    );
69
70    container.delete_sub_property(clone_name, 0)?;
71    container.delete_sub_property(name, 0)?;
72    println!(
73        "  removed both: {name} exists={}, {clone_name} exists={}",
74        container.exists(name, 0)?,
75        container.exists(clone_name, 0)?
76    );
77    Ok(())
78}
More examples
Hide additional examples
examples/template_manage_complex.rs (line 126)
122fn variable_template(engine: &Engine) -> Result<PropertyObject, Error> {
123    let variable =
124        engine.new_property_object(PropValType::String, false, "", PropertyOptions::NONE.bits())?;
125    variable.set_name(VARIABLE_TEMPLATE)?;
126    variable.set_val_string("", PropertyOptions::NONE.bits(), "Template Variable Value")?;
127    Ok(variable)
128}
examples/result_list_parse.rs (line 45)
36fn add_pass_fail(
37    engine: &Engine,
38    sequence: &Sequence,
39    name: &str,
40    source: &str,
41) -> Result<(), Error> {
42    let step = engine.new_step(AdapterKeyName::NoneAdapter.as_str(), "PassFailTest")?;
43    step.set_name(name)?;
44    step.as_property_object()?
45        .set_val_string("DataSource", insert_if_missing(), source)?;
46    sequence.insert_step(
47        &step,
48        sequence.get_num_steps(StepGroup::Main)?,
49        StepGroup::Main,
50    )
51}
52
53/// Adds a numeric limit test with a fixed measurement.
54fn add_numeric(
55    engine: &Engine,
56    sequence: &Sequence,
57    name: &str,
58    source: &str,
59    low: f64,
60    high: f64,
61) -> Result<(), Error> {
62    let step = engine.new_step(AdapterKeyName::NoneAdapter.as_str(), "NumericLimitTest")?;
63    step.set_name(name)?;
64    let properties = step.as_property_object()?;
65    properties.set_val_string("DataSource", insert_if_missing(), source)?;
66    properties.set_val_number("Limits.Low", insert_if_missing(), low)?;
67    properties.set_val_number("Limits.High", insert_if_missing(), high)?;
68    sequence.insert_step(
69        &step,
70        sequence.get_num_steps(StepGroup::Main)?,
71        StepGroup::Main,
72    )
73}
examples/ui_messages_handle.rs (line 31)
22fn add_statement(
23    engine: &Engine,
24    sequence: &Sequence,
25    name: &str,
26    expression: &str,
27) -> Result<(), rs_teststand::Error> {
28    let step = engine.new_step(NO_ADAPTER, "Statement")?;
29    step.set_name(name)?;
30    step.as_property_object()?
31        .set_val_string("TS.PostExpr", INSERT_IF_MISSING, expression)?;
32    sequence.insert_step(
33        &step,
34        sequence.get_num_steps(StepGroup::Main)?,
35        StepGroup::Main,
36    )?;
37    Ok(())
38}
examples/data_type_manage.rs (line 32)
28fn build_multimeter_type(engine: &Engine) -> Result<PropertyObject, rs_teststand::Error> {
29    let data_type = engine.new_property_object(PropValType::Container, false, "", NO_OPTIONS)?;
30    data_type.set_val_number("Resolution", INSERT_IF_MISSING, 6.5)?;
31    data_type.set_val_bool("AutoZero", INSERT_IF_MISSING, false)?;
32    data_type.set_val_string("Mode", INSERT_IF_MISSING, "Voltage")?;
33    data_type.set_val_number("Range", INSERT_IF_MISSING, 100.0)?;
34    data_type.set_name("DigitalMultimeter")?;
35    Ok(data_type)
36}
37
38/// Builds the array `UpdateEnumerators` expects.
39///
40/// One container per enumerator, each carrying `EnumeratorName` and
41/// `EnumeratorValue`. Strictness is an attribute of the array, not a member of
42/// it, a strict enumeration refuses values outside the declared set.
43fn enumerator_array(
44    engine: &Engine,
45    named_values: &[(&str, f64)],
46    strict: bool,
47) -> Result<PropertyObject, rs_teststand::Error> {
48    let array = engine.new_property_object(PropValType::Container, true, "", NO_OPTIONS)?;
49    array.set_num_elements(i32::try_from(named_values.len()).unwrap_or(0), NO_OPTIONS)?;
50    for (index, (name, value)) in named_values.iter().enumerate() {
51        let element =
52            array.get_property_object_by_offset(i32::try_from(index).unwrap_or(0), NO_OPTIONS)?;
53        element.set_val_string("EnumeratorName", INSERT_IF_MISSING, name)?;
54        element.set_val_number("EnumeratorValue", INSERT_IF_MISSING, *value)?;
55    }
56    array
57        .attributes()?
58        .set_val_bool(IS_STRICT_ATTRIBUTE, INSERT_IF_MISSING, strict)?;
59    Ok(array)
60}
examples/step_insert_from_template.rs (lines 57-61)
51fn build_prototype(engine: &Engine) -> Result<Step, Error> {
52    // The maintained LabVIEW adapter. The standard-prototype key still exists
53    // but the documentation marks it obsolete in favour of this one.
54    let step = engine.new_step(AdapterKeyName::LabView.as_str(), "Action")?;
55    step.set_name("Measure (VI)")?;
56    step.set_run_mode(RunMode::Normal)?;
57    step.as_property_object()?.set_val_string(
58        VI_PATH,
59        PropertyOptions::INSERT_IF_MISSING.bits(),
60        r"example.lvlibp\measure.vi",
61    )?;
62    Ok(step)
63}
Source

pub fn get_val_number( &self, lookup_string: &str, options: i32, ) -> Result<f64, Error>

Reads a numeric property by lookup path (PropertyObject.GetValNumber).

§Errors

Error if the COM call fails or returns an unexpected type.

Examples found in repository?
examples/data_type_manage.rs (line 98)
86fn print_enumerators(definition: &PropertyObject) -> Result<(), rs_teststand::Error> {
87    let enumerators = definition.enumerators()?;
88    let strict = enumerators
89        .attributes()?
90        .get_val_bool(IS_STRICT_ATTRIBUTE, NO_OPTIONS)?;
91    println!(
92        "  {} v{} (strict={strict}):",
93        definition.name()?,
94        definition.type_version()?
95    );
96    for index in 0..enumerators.get_num_elements()? {
97        let element = enumerators.get_property_object_by_offset(index, NO_OPTIONS)?;
98        let value = element.get_val_number("", COERCE_TO_NUMBER)?;
99        println!(
100            "    {} -> {value}",
101            element.get_value_display_name("", NO_OPTIONS)?
102        );
103    }
104    Ok(())
105}
More examples
Hide additional examples
examples/sequence_build.rs (line 56)
46fn describe(sequence: &Sequence) -> Result<(), rs_teststand::Error> {
47    let count = sequence.get_num_steps(StepGroup::Main)?;
48    println!("{} holds {count} step(s) in Main:", sequence.name()?);
49    for index in 0..count {
50        let step = sequence.get_step(index, StepGroup::Main)?;
51        let properties = step.as_property_object()?;
52        println!("  [{index}] {}", step.name()?);
53        if properties.exists("Limits.Low", NO_OPTIONS)? {
54            println!(
55                "      limits: low={}, high={}",
56                properties.get_val_number("Limits.Low", NO_OPTIONS)?,
57                properties.get_val_number("Limits.High", NO_OPTIONS)?
58            );
59        }
60        let precondition = step.precondition()?;
61        if !precondition.is_empty() {
62            println!("      runs when: {precondition}");
63        }
64    }
65    Ok(())
66}
examples/execution_run_test_headless.rs (line 138)
114fn report(results: &rs_teststand::PropertyObject) -> Result<(), Error> {
115    if !results.exists("ResultList", none())? {
116        println!("No results recorded.");
117        return Ok(());
118    }
119    let result_list = results.get_property_object("ResultList", none())?;
120    let count = result_list.get_num_elements()?;
121    println!(
122        "\n{count} of {} defined test(s) recorded a result:",
123        TESTS.len()
124    );
125    if count < i32::try_from(TESTS.len()).unwrap_or(i32::MAX) {
126        println!("  (a failure ended the run before the rest could execute)");
127    }
128
129    for index in 0..count {
130        let entry = result_list.get_property_object_by_offset(index, none())?;
131        let name = entry
132            .get_val_string("TS.StepName", none())
133            .unwrap_or_else(|_| "<unnamed>".to_owned());
134        let status = entry
135            .get_val_string("Status", none())
136            .unwrap_or_else(|_| "<no status>".to_owned());
137        // Only test steps record a measurement; an Action has no Numeric.
138        match entry.get_val_number("Numeric", none()) {
139            Ok(measured) => println!("  {name}: {status} (measured {measured})"),
140            Err(_) => println!("  {name}: {status}"),
141        }
142    }
143    Ok(())
144}
examples/variables_manage.rs (line 59)
38fn temporary_variable_lifecycle(container: &PropertyObject) -> Result<(), rs_teststand::Error> {
39    let name = "TempScratch";
40    let clone_name = "TempScratchCopy";
41    for stale in [name, clone_name] {
42        if container.exists(stale, 0)? {
43            container.delete_sub_property(stale, 0)?;
44        }
45    }
46
47    container.new_sub_property(name, PropValType::String, false, "", INSERT_IF_MISSING)?;
48    container.set_val_string(name, 0, "scratch")?;
49    println!(
50        "  created {name} (String) = '{}'",
51        container.get_val_string(name, 0)?
52    );
53
54    container.delete_sub_property(name, 0)?;
55    container.new_sub_property(name, PropValType::Number, false, "", INSERT_IF_MISSING)?;
56    container.set_val_number(name, 0, 42.0)?;
57    println!(
58        "  retyped {name} -> Number = {}",
59        container.get_val_number(name, 0)?
60    );
61
62    // clone copies value and type; set_property_object attaches it under a new name.
63    let copy = container.clone_property(name, 0)?;
64    container.set_property_object(clone_name, INSERT_IF_MISSING, &copy)?;
65    println!(
66        "  cloned  {name} -> {clone_name} = {}",
67        container.get_val_number(clone_name, 0)?
68    );
69
70    container.delete_sub_property(clone_name, 0)?;
71    container.delete_sub_property(name, 0)?;
72    println!(
73        "  removed both: {name} exists={}, {clone_name} exists={}",
74        container.exists(name, 0)?,
75        container.exists(clone_name, 0)?
76    );
77    Ok(())
78}
79
80/// Opens a sequence file if one was named on the command line.
81///
82/// Locals, parameters and file globals all need a real file; without one the
83/// example still demonstrates station globals.
84fn open_sequence_file(engine: &Engine) -> Result<Option<SequenceFile>, rs_teststand::Error> {
85    let Some(path) = std::env::args().nth(1) else {
86        return Ok(None);
87    };
88    let file = engine.get_sequence_file_ex(
89        &path,
90        GetSeqFileOptions::DO_NOT_RUN_LOAD_CALLBACK,
91        ConflictHandler::Error,
92    )?;
93    Ok(Some(file))
94}
95
96/// Everything that lives on the station rather than in a file.
97///
98/// Separated because it is the one scope that outlives the process, so it is
99/// the one worth reading on its own.
100fn show_station_globals(engine: &Engine) -> Result<(), rs_teststand::Error> {
101    // Station globals: shared by every sequence file, and written to disk.
102    let station_globals = engine.globals()?;
103    if !station_globals.exists("StationInfo", 0)? {
104        station_globals.new_sub_property(
105            "StationInfo",
106            PropValType::Container,
107            false,
108            "",
109            INSERT_IF_MISSING,
110        )?;
111    }
112    let station_info = station_globals.get_property_object("StationInfo", 0)?;
113
114    // A container holds mixed types, so one station record can carry the name,
115    // a count and a flag without three separate globals.
116    set_string(&station_info, "StationName", "STATION_01")?;
117    station_info.set_val_number("CalibrationIntervalDays", INSERT_IF_MISSING, 90.0)?;
118    station_info.set_val_bool("FixtureInstalled", INSERT_IF_MISSING, true)?;
119
120    // A 64-bit count. `SetValNumber` stores a double, which starts losing whole
121    // numbers past 2^53, so anything that counts for the life of a station wants
122    // the integer path instead.
123    station_info.set_val_integer64("UnitsTestedTotal", INSERT_IF_MISSING, 9_007_199_254_740_993)?;
124
125    // A nested container, because station data is rarely one level deep.
126    if !station_info.exists("LastCalibration", 0)? {
127        station_info.new_sub_property(
128            "LastCalibration",
129            PropValType::Container,
130            false,
131            "",
132            INSERT_IF_MISSING,
133        )?;
134    }
135    let calibration = station_info.get_property_object("LastCalibration", 0)?;
136    set_string(&calibration, "Technician", "R. Alvarez")?;
137    set_string(&calibration, "Date", "2026-06-14")?;
138
139    println!("StationGlobals.StationInfo:");
140    println!(
141        "  StationName             = '{}'",
142        station_info.get_val_string("StationName", 0)?
143    );
144    println!(
145        "  CalibrationIntervalDays = {}",
146        station_info.get_val_number("CalibrationIntervalDays", 0)?
147    );
148    println!(
149        "  FixtureInstalled        = {}",
150        station_info.get_val_bool("FixtureInstalled", 0)?
151    );
152    println!(
153        "  UnitsTestedTotal        = {}",
154        station_info.get_val_integer64("UnitsTestedTotal", 0)?
155    );
156    println!(
157        "  LastCalibration.Technician = '{}' on {}",
158        calibration.get_val_string("Technician", 0)?,
159        calibration.get_val_string("Date", 0)?
160    );
161
162    // Walk the container rather than naming each field, which is what a host
163    // does when it does not know the shape in advance.
164    println!(
165        "  walked, {} field(s):",
166        station_info.get_num_sub_properties("")?
167    );
168    for index in 0..station_info.get_num_sub_properties("")? {
169        println!(
170            "    {}",
171            station_info.get_nth_sub_property_name("", index, 0)?
172        );
173    }
174
175    // Station globals live in memory until this is called. Without it the values
176    // above are gone when the engine goes away, which is the difference between
177    // a station global and a file global.
178    // `false` means do not prompt if another process changed the file first.
179    // An example must never raise a dialog, and neither must a headless host.
180    engine.commit_globals_to_disk(false)?;
181    println!("  committed to disk");
182
183    Ok(())
184}
Source

pub fn set_val_number( &self, lookup_string: &str, options: i32, value: f64, ) -> Result<(), Error>

Writes a numeric property by lookup path (PropertyObject.SetValNumber).

§Errors

Error if the COM call fails.

Examples found in repository?
examples/data_type_manage.rs (line 30)
28fn build_multimeter_type(engine: &Engine) -> Result<PropertyObject, rs_teststand::Error> {
29    let data_type = engine.new_property_object(PropValType::Container, false, "", NO_OPTIONS)?;
30    data_type.set_val_number("Resolution", INSERT_IF_MISSING, 6.5)?;
31    data_type.set_val_bool("AutoZero", INSERT_IF_MISSING, false)?;
32    data_type.set_val_string("Mode", INSERT_IF_MISSING, "Voltage")?;
33    data_type.set_val_number("Range", INSERT_IF_MISSING, 100.0)?;
34    data_type.set_name("DigitalMultimeter")?;
35    Ok(data_type)
36}
37
38/// Builds the array `UpdateEnumerators` expects.
39///
40/// One container per enumerator, each carrying `EnumeratorName` and
41/// `EnumeratorValue`. Strictness is an attribute of the array, not a member of
42/// it, a strict enumeration refuses values outside the declared set.
43fn enumerator_array(
44    engine: &Engine,
45    named_values: &[(&str, f64)],
46    strict: bool,
47) -> Result<PropertyObject, rs_teststand::Error> {
48    let array = engine.new_property_object(PropValType::Container, true, "", NO_OPTIONS)?;
49    array.set_num_elements(i32::try_from(named_values.len()).unwrap_or(0), NO_OPTIONS)?;
50    for (index, (name, value)) in named_values.iter().enumerate() {
51        let element =
52            array.get_property_object_by_offset(i32::try_from(index).unwrap_or(0), NO_OPTIONS)?;
53        element.set_val_string("EnumeratorName", INSERT_IF_MISSING, name)?;
54        element.set_val_number("EnumeratorValue", INSERT_IF_MISSING, *value)?;
55    }
56    array
57        .attributes()?
58        .set_val_bool(IS_STRICT_ATTRIBUTE, INSERT_IF_MISSING, strict)?;
59    Ok(array)
60}
More examples
Hide additional examples
examples/sequence_build.rs (line 41)
28fn numeric_limit_test(
29    engine: &Engine,
30    name: &str,
31    precondition: &str,
32    low: f64,
33    high: f64,
34) -> Result<Step, rs_teststand::Error> {
35    let step = engine.new_step(NO_ADAPTER, "NumericLimitTest")?;
36    step.set_name(name)?;
37    step.set_precondition(precondition)?;
38    step.set_record_result(true)?;
39
40    let properties = step.as_property_object()?;
41    properties.set_val_number("Limits.High", INSERT_IF_MISSING, high)?;
42    properties.set_val_number("Limits.Low", INSERT_IF_MISSING, low)?;
43    Ok(step)
44}
examples/result_list_parse.rs (line 66)
54fn add_numeric(
55    engine: &Engine,
56    sequence: &Sequence,
57    name: &str,
58    source: &str,
59    low: f64,
60    high: f64,
61) -> Result<(), Error> {
62    let step = engine.new_step(AdapterKeyName::NoneAdapter.as_str(), "NumericLimitTest")?;
63    step.set_name(name)?;
64    let properties = step.as_property_object()?;
65    properties.set_val_string("DataSource", insert_if_missing(), source)?;
66    properties.set_val_number("Limits.Low", insert_if_missing(), low)?;
67    properties.set_val_number("Limits.High", insert_if_missing(), high)?;
68    sequence.insert_step(
69        &step,
70        sequence.get_num_steps(StepGroup::Main)?,
71        StepGroup::Main,
72    )
73}
examples/execution_run_test_headless.rs (line 76)
62fn add_numeric_limit_test(
63    engine: &Engine,
64    sequence: &Sequence,
65    name: &str,
66    data_source: &str,
67    low: f64,
68    high: f64,
69) -> Result<(), Error> {
70    let step = engine.new_step(AdapterKeyName::NoneAdapter.as_str(), "NumericLimitTest")?;
71    step.set_name(name)?;
72    step.set_record_result(true)?;
73
74    let properties = step.as_property_object()?;
75    properties.set_val_string("DataSource", insert_if_missing(), data_source)?;
76    properties.set_val_number("Limits.Low", insert_if_missing(), low)?;
77    properties.set_val_number("Limits.High", insert_if_missing(), high)?;
78
79    sequence.insert_step(
80        &step,
81        sequence.get_num_steps(StepGroup::Main)?,
82        StepGroup::Main,
83    )
84}
examples/variables_manage.rs (line 56)
38fn temporary_variable_lifecycle(container: &PropertyObject) -> Result<(), rs_teststand::Error> {
39    let name = "TempScratch";
40    let clone_name = "TempScratchCopy";
41    for stale in [name, clone_name] {
42        if container.exists(stale, 0)? {
43            container.delete_sub_property(stale, 0)?;
44        }
45    }
46
47    container.new_sub_property(name, PropValType::String, false, "", INSERT_IF_MISSING)?;
48    container.set_val_string(name, 0, "scratch")?;
49    println!(
50        "  created {name} (String) = '{}'",
51        container.get_val_string(name, 0)?
52    );
53
54    container.delete_sub_property(name, 0)?;
55    container.new_sub_property(name, PropValType::Number, false, "", INSERT_IF_MISSING)?;
56    container.set_val_number(name, 0, 42.0)?;
57    println!(
58        "  retyped {name} -> Number = {}",
59        container.get_val_number(name, 0)?
60    );
61
62    // clone copies value and type; set_property_object attaches it under a new name.
63    let copy = container.clone_property(name, 0)?;
64    container.set_property_object(clone_name, INSERT_IF_MISSING, &copy)?;
65    println!(
66        "  cloned  {name} -> {clone_name} = {}",
67        container.get_val_number(clone_name, 0)?
68    );
69
70    container.delete_sub_property(clone_name, 0)?;
71    container.delete_sub_property(name, 0)?;
72    println!(
73        "  removed both: {name} exists={}, {clone_name} exists={}",
74        container.exists(name, 0)?,
75        container.exists(clone_name, 0)?
76    );
77    Ok(())
78}
79
80/// Opens a sequence file if one was named on the command line.
81///
82/// Locals, parameters and file globals all need a real file; without one the
83/// example still demonstrates station globals.
84fn open_sequence_file(engine: &Engine) -> Result<Option<SequenceFile>, rs_teststand::Error> {
85    let Some(path) = std::env::args().nth(1) else {
86        return Ok(None);
87    };
88    let file = engine.get_sequence_file_ex(
89        &path,
90        GetSeqFileOptions::DO_NOT_RUN_LOAD_CALLBACK,
91        ConflictHandler::Error,
92    )?;
93    Ok(Some(file))
94}
95
96/// Everything that lives on the station rather than in a file.
97///
98/// Separated because it is the one scope that outlives the process, so it is
99/// the one worth reading on its own.
100fn show_station_globals(engine: &Engine) -> Result<(), rs_teststand::Error> {
101    // Station globals: shared by every sequence file, and written to disk.
102    let station_globals = engine.globals()?;
103    if !station_globals.exists("StationInfo", 0)? {
104        station_globals.new_sub_property(
105            "StationInfo",
106            PropValType::Container,
107            false,
108            "",
109            INSERT_IF_MISSING,
110        )?;
111    }
112    let station_info = station_globals.get_property_object("StationInfo", 0)?;
113
114    // A container holds mixed types, so one station record can carry the name,
115    // a count and a flag without three separate globals.
116    set_string(&station_info, "StationName", "STATION_01")?;
117    station_info.set_val_number("CalibrationIntervalDays", INSERT_IF_MISSING, 90.0)?;
118    station_info.set_val_bool("FixtureInstalled", INSERT_IF_MISSING, true)?;
119
120    // A 64-bit count. `SetValNumber` stores a double, which starts losing whole
121    // numbers past 2^53, so anything that counts for the life of a station wants
122    // the integer path instead.
123    station_info.set_val_integer64("UnitsTestedTotal", INSERT_IF_MISSING, 9_007_199_254_740_993)?;
124
125    // A nested container, because station data is rarely one level deep.
126    if !station_info.exists("LastCalibration", 0)? {
127        station_info.new_sub_property(
128            "LastCalibration",
129            PropValType::Container,
130            false,
131            "",
132            INSERT_IF_MISSING,
133        )?;
134    }
135    let calibration = station_info.get_property_object("LastCalibration", 0)?;
136    set_string(&calibration, "Technician", "R. Alvarez")?;
137    set_string(&calibration, "Date", "2026-06-14")?;
138
139    println!("StationGlobals.StationInfo:");
140    println!(
141        "  StationName             = '{}'",
142        station_info.get_val_string("StationName", 0)?
143    );
144    println!(
145        "  CalibrationIntervalDays = {}",
146        station_info.get_val_number("CalibrationIntervalDays", 0)?
147    );
148    println!(
149        "  FixtureInstalled        = {}",
150        station_info.get_val_bool("FixtureInstalled", 0)?
151    );
152    println!(
153        "  UnitsTestedTotal        = {}",
154        station_info.get_val_integer64("UnitsTestedTotal", 0)?
155    );
156    println!(
157        "  LastCalibration.Technician = '{}' on {}",
158        calibration.get_val_string("Technician", 0)?,
159        calibration.get_val_string("Date", 0)?
160    );
161
162    // Walk the container rather than naming each field, which is what a host
163    // does when it does not know the shape in advance.
164    println!(
165        "  walked, {} field(s):",
166        station_info.get_num_sub_properties("")?
167    );
168    for index in 0..station_info.get_num_sub_properties("")? {
169        println!(
170            "    {}",
171            station_info.get_nth_sub_property_name("", index, 0)?
172        );
173    }
174
175    // Station globals live in memory until this is called. Without it the values
176    // above are gone when the engine goes away, which is the difference between
177    // a station global and a file global.
178    // `false` means do not prompt if another process changed the file first.
179    // An example must never raise a dialog, and neither must a headless host.
180    engine.commit_globals_to_disk(false)?;
181    println!("  committed to disk");
182
183    Ok(())
184}
Source

pub fn get_val_bool( &self, lookup_string: &str, options: i32, ) -> Result<bool, Error>

Reads a boolean property by lookup path (PropertyObject.GetValBoolean).

§Errors

Error if the COM call fails or returns an unexpected type.

Examples found in repository?
examples/data_type_manage.rs (line 90)
86fn print_enumerators(definition: &PropertyObject) -> Result<(), rs_teststand::Error> {
87    let enumerators = definition.enumerators()?;
88    let strict = enumerators
89        .attributes()?
90        .get_val_bool(IS_STRICT_ATTRIBUTE, NO_OPTIONS)?;
91    println!(
92        "  {} v{} (strict={strict}):",
93        definition.name()?,
94        definition.type_version()?
95    );
96    for index in 0..enumerators.get_num_elements()? {
97        let element = enumerators.get_property_object_by_offset(index, NO_OPTIONS)?;
98        let value = element.get_val_number("", COERCE_TO_NUMBER)?;
99        println!(
100            "    {} -> {value}",
101            element.get_value_display_name("", NO_OPTIONS)?
102        );
103    }
104    Ok(())
105}
More examples
Hide additional examples
examples/variables_manage.rs (line 150)
100fn show_station_globals(engine: &Engine) -> Result<(), rs_teststand::Error> {
101    // Station globals: shared by every sequence file, and written to disk.
102    let station_globals = engine.globals()?;
103    if !station_globals.exists("StationInfo", 0)? {
104        station_globals.new_sub_property(
105            "StationInfo",
106            PropValType::Container,
107            false,
108            "",
109            INSERT_IF_MISSING,
110        )?;
111    }
112    let station_info = station_globals.get_property_object("StationInfo", 0)?;
113
114    // A container holds mixed types, so one station record can carry the name,
115    // a count and a flag without three separate globals.
116    set_string(&station_info, "StationName", "STATION_01")?;
117    station_info.set_val_number("CalibrationIntervalDays", INSERT_IF_MISSING, 90.0)?;
118    station_info.set_val_bool("FixtureInstalled", INSERT_IF_MISSING, true)?;
119
120    // A 64-bit count. `SetValNumber` stores a double, which starts losing whole
121    // numbers past 2^53, so anything that counts for the life of a station wants
122    // the integer path instead.
123    station_info.set_val_integer64("UnitsTestedTotal", INSERT_IF_MISSING, 9_007_199_254_740_993)?;
124
125    // A nested container, because station data is rarely one level deep.
126    if !station_info.exists("LastCalibration", 0)? {
127        station_info.new_sub_property(
128            "LastCalibration",
129            PropValType::Container,
130            false,
131            "",
132            INSERT_IF_MISSING,
133        )?;
134    }
135    let calibration = station_info.get_property_object("LastCalibration", 0)?;
136    set_string(&calibration, "Technician", "R. Alvarez")?;
137    set_string(&calibration, "Date", "2026-06-14")?;
138
139    println!("StationGlobals.StationInfo:");
140    println!(
141        "  StationName             = '{}'",
142        station_info.get_val_string("StationName", 0)?
143    );
144    println!(
145        "  CalibrationIntervalDays = {}",
146        station_info.get_val_number("CalibrationIntervalDays", 0)?
147    );
148    println!(
149        "  FixtureInstalled        = {}",
150        station_info.get_val_bool("FixtureInstalled", 0)?
151    );
152    println!(
153        "  UnitsTestedTotal        = {}",
154        station_info.get_val_integer64("UnitsTestedTotal", 0)?
155    );
156    println!(
157        "  LastCalibration.Technician = '{}' on {}",
158        calibration.get_val_string("Technician", 0)?,
159        calibration.get_val_string("Date", 0)?
160    );
161
162    // Walk the container rather than naming each field, which is what a host
163    // does when it does not know the shape in advance.
164    println!(
165        "  walked, {} field(s):",
166        station_info.get_num_sub_properties("")?
167    );
168    for index in 0..station_info.get_num_sub_properties("")? {
169        println!(
170            "    {}",
171            station_info.get_nth_sub_property_name("", index, 0)?
172        );
173    }
174
175    // Station globals live in memory until this is called. Without it the values
176    // above are gone when the engine goes away, which is the difference between
177    // a station global and a file global.
178    // `false` means do not prompt if another process changed the file first.
179    // An example must never raise a dialog, and neither must a headless host.
180    engine.commit_globals_to_disk(false)?;
181    println!("  committed to disk");
182
183    Ok(())
184}
Source

pub fn set_val_bool( &self, lookup_string: &str, options: i32, value: bool, ) -> Result<(), Error>

Writes a boolean property by lookup path (PropertyObject.SetValBoolean).

§Errors

Error if the COM call fails.

Examples found in repository?
examples/data_type_manage.rs (line 31)
28fn build_multimeter_type(engine: &Engine) -> Result<PropertyObject, rs_teststand::Error> {
29    let data_type = engine.new_property_object(PropValType::Container, false, "", NO_OPTIONS)?;
30    data_type.set_val_number("Resolution", INSERT_IF_MISSING, 6.5)?;
31    data_type.set_val_bool("AutoZero", INSERT_IF_MISSING, false)?;
32    data_type.set_val_string("Mode", INSERT_IF_MISSING, "Voltage")?;
33    data_type.set_val_number("Range", INSERT_IF_MISSING, 100.0)?;
34    data_type.set_name("DigitalMultimeter")?;
35    Ok(data_type)
36}
37
38/// Builds the array `UpdateEnumerators` expects.
39///
40/// One container per enumerator, each carrying `EnumeratorName` and
41/// `EnumeratorValue`. Strictness is an attribute of the array, not a member of
42/// it, a strict enumeration refuses values outside the declared set.
43fn enumerator_array(
44    engine: &Engine,
45    named_values: &[(&str, f64)],
46    strict: bool,
47) -> Result<PropertyObject, rs_teststand::Error> {
48    let array = engine.new_property_object(PropValType::Container, true, "", NO_OPTIONS)?;
49    array.set_num_elements(i32::try_from(named_values.len()).unwrap_or(0), NO_OPTIONS)?;
50    for (index, (name, value)) in named_values.iter().enumerate() {
51        let element =
52            array.get_property_object_by_offset(i32::try_from(index).unwrap_or(0), NO_OPTIONS)?;
53        element.set_val_string("EnumeratorName", INSERT_IF_MISSING, name)?;
54        element.set_val_number("EnumeratorValue", INSERT_IF_MISSING, *value)?;
55    }
56    array
57        .attributes()?
58        .set_val_bool(IS_STRICT_ATTRIBUTE, INSERT_IF_MISSING, strict)?;
59    Ok(array)
60}
More examples
Hide additional examples
examples/variables_manage.rs (line 118)
100fn show_station_globals(engine: &Engine) -> Result<(), rs_teststand::Error> {
101    // Station globals: shared by every sequence file, and written to disk.
102    let station_globals = engine.globals()?;
103    if !station_globals.exists("StationInfo", 0)? {
104        station_globals.new_sub_property(
105            "StationInfo",
106            PropValType::Container,
107            false,
108            "",
109            INSERT_IF_MISSING,
110        )?;
111    }
112    let station_info = station_globals.get_property_object("StationInfo", 0)?;
113
114    // A container holds mixed types, so one station record can carry the name,
115    // a count and a flag without three separate globals.
116    set_string(&station_info, "StationName", "STATION_01")?;
117    station_info.set_val_number("CalibrationIntervalDays", INSERT_IF_MISSING, 90.0)?;
118    station_info.set_val_bool("FixtureInstalled", INSERT_IF_MISSING, true)?;
119
120    // A 64-bit count. `SetValNumber` stores a double, which starts losing whole
121    // numbers past 2^53, so anything that counts for the life of a station wants
122    // the integer path instead.
123    station_info.set_val_integer64("UnitsTestedTotal", INSERT_IF_MISSING, 9_007_199_254_740_993)?;
124
125    // A nested container, because station data is rarely one level deep.
126    if !station_info.exists("LastCalibration", 0)? {
127        station_info.new_sub_property(
128            "LastCalibration",
129            PropValType::Container,
130            false,
131            "",
132            INSERT_IF_MISSING,
133        )?;
134    }
135    let calibration = station_info.get_property_object("LastCalibration", 0)?;
136    set_string(&calibration, "Technician", "R. Alvarez")?;
137    set_string(&calibration, "Date", "2026-06-14")?;
138
139    println!("StationGlobals.StationInfo:");
140    println!(
141        "  StationName             = '{}'",
142        station_info.get_val_string("StationName", 0)?
143    );
144    println!(
145        "  CalibrationIntervalDays = {}",
146        station_info.get_val_number("CalibrationIntervalDays", 0)?
147    );
148    println!(
149        "  FixtureInstalled        = {}",
150        station_info.get_val_bool("FixtureInstalled", 0)?
151    );
152    println!(
153        "  UnitsTestedTotal        = {}",
154        station_info.get_val_integer64("UnitsTestedTotal", 0)?
155    );
156    println!(
157        "  LastCalibration.Technician = '{}' on {}",
158        calibration.get_val_string("Technician", 0)?,
159        calibration.get_val_string("Date", 0)?
160    );
161
162    // Walk the container rather than naming each field, which is what a host
163    // does when it does not know the shape in advance.
164    println!(
165        "  walked, {} field(s):",
166        station_info.get_num_sub_properties("")?
167    );
168    for index in 0..station_info.get_num_sub_properties("")? {
169        println!(
170            "    {}",
171            station_info.get_nth_sub_property_name("", index, 0)?
172        );
173    }
174
175    // Station globals live in memory until this is called. Without it the values
176    // above are gone when the engine goes away, which is the difference between
177    // a station global and a file global.
178    // `false` means do not prompt if another process changed the file first.
179    // An example must never raise a dialog, and neither must a headless host.
180    engine.commit_globals_to_disk(false)?;
181    println!("  committed to disk");
182
183    Ok(())
184}
Source

pub fn get_num_sub_properties(&self, lookup_string: &str) -> Result<i32, Error>

How many sub-properties sit directly under lookup_string (GetNumSubProperties).

Pass an empty string for this object itself.

§Errors

Error if the COM call fails or returns an unexpected type.

Examples found in repository?
examples/variables_manage.rs (line 166)
100fn show_station_globals(engine: &Engine) -> Result<(), rs_teststand::Error> {
101    // Station globals: shared by every sequence file, and written to disk.
102    let station_globals = engine.globals()?;
103    if !station_globals.exists("StationInfo", 0)? {
104        station_globals.new_sub_property(
105            "StationInfo",
106            PropValType::Container,
107            false,
108            "",
109            INSERT_IF_MISSING,
110        )?;
111    }
112    let station_info = station_globals.get_property_object("StationInfo", 0)?;
113
114    // A container holds mixed types, so one station record can carry the name,
115    // a count and a flag without three separate globals.
116    set_string(&station_info, "StationName", "STATION_01")?;
117    station_info.set_val_number("CalibrationIntervalDays", INSERT_IF_MISSING, 90.0)?;
118    station_info.set_val_bool("FixtureInstalled", INSERT_IF_MISSING, true)?;
119
120    // A 64-bit count. `SetValNumber` stores a double, which starts losing whole
121    // numbers past 2^53, so anything that counts for the life of a station wants
122    // the integer path instead.
123    station_info.set_val_integer64("UnitsTestedTotal", INSERT_IF_MISSING, 9_007_199_254_740_993)?;
124
125    // A nested container, because station data is rarely one level deep.
126    if !station_info.exists("LastCalibration", 0)? {
127        station_info.new_sub_property(
128            "LastCalibration",
129            PropValType::Container,
130            false,
131            "",
132            INSERT_IF_MISSING,
133        )?;
134    }
135    let calibration = station_info.get_property_object("LastCalibration", 0)?;
136    set_string(&calibration, "Technician", "R. Alvarez")?;
137    set_string(&calibration, "Date", "2026-06-14")?;
138
139    println!("StationGlobals.StationInfo:");
140    println!(
141        "  StationName             = '{}'",
142        station_info.get_val_string("StationName", 0)?
143    );
144    println!(
145        "  CalibrationIntervalDays = {}",
146        station_info.get_val_number("CalibrationIntervalDays", 0)?
147    );
148    println!(
149        "  FixtureInstalled        = {}",
150        station_info.get_val_bool("FixtureInstalled", 0)?
151    );
152    println!(
153        "  UnitsTestedTotal        = {}",
154        station_info.get_val_integer64("UnitsTestedTotal", 0)?
155    );
156    println!(
157        "  LastCalibration.Technician = '{}' on {}",
158        calibration.get_val_string("Technician", 0)?,
159        calibration.get_val_string("Date", 0)?
160    );
161
162    // Walk the container rather than naming each field, which is what a host
163    // does when it does not know the shape in advance.
164    println!(
165        "  walked, {} field(s):",
166        station_info.get_num_sub_properties("")?
167    );
168    for index in 0..station_info.get_num_sub_properties("")? {
169        println!(
170            "    {}",
171            station_info.get_nth_sub_property_name("", index, 0)?
172        );
173    }
174
175    // Station globals live in memory until this is called. Without it the values
176    // above are gone when the engine goes away, which is the difference between
177    // a station global and a file global.
178    // `false` means do not prompt if another process changed the file first.
179    // An example must never raise a dialog, and neither must a headless host.
180    engine.commit_globals_to_disk(false)?;
181    println!("  committed to disk");
182
183    Ok(())
184}
More examples
Hide additional examples
examples/users_manage.rs (line 71)
29fn main() -> Result<(), rs_teststand::Error> {
30    let engine = Engine::new()?;
31
32    // Passing no profile means the user inherits nothing.
33    let user = engine.new_user(None)?;
34    user.set_login_name("operator1")?;
35    user.set_full_name("Test Operator")?;
36    user.set_password("ts-secret")?;
37
38    println!("User: {} ({})", user.login_name()?, user.full_name()?);
39
40    // Check a credential without handling the stored value.
41    println!(
42        "  password 'ts-secret' valid: {}",
43        user.validate_password("ts-secret")?
44    );
45    println!(
46        "  password 'wrong' valid:     {}",
47        user.validate_password("wrong")?
48    );
49
50    println!("  privilege checks:");
51    report_privileges(
52        &user,
53        &[
54            UserPrivilege::Operate,
55            UserPrivilege::Execute,
56            UserPrivilege::Develop,
57            UserPrivilege::Debug,
58            UserPrivilege::EditUsers,
59        ],
60    )?;
61
62    // A privilege can also be named by its full path, which is how a leaf
63    // inside a category is reached.
64    println!(
65        "  Debug.RunSelectedTests:  {}",
66        user.has_privilege_named("Debug.RunSelectedTests")?
67    );
68
69    // The privilege tree is nested: categories hold the individual rights.
70    let privileges = user.privileges()?;
71    let count = privileges.get_num_sub_properties("")?;
72    println!("  privilege tree ({count} categories):");
73    for index in 0..count {
74        let name = privileges.get_nth_sub_property_name("", index, 0)?;
75        let members = privileges
76            .get_property_object(&name, 0)?
77            .get_num_sub_properties("")?;
78        println!("    {name:<12} {members} member(s)");
79    }
80
81    // The station itself, read-only.
82    println!("\nStation:");
83    match engine.current_user()? {
84        Some(current) => println!("  logged in as {}", current.login_name()?),
85        None => println!("  nobody logged in (usual when login is not required)"),
86    }
87    println!(
88        "  is 'operator1' a real account here? {}",
89        engine.user_name_exists("operator1")?
90    );
91
92    // Where accounts actually live. Everything above this point exists only in
93    // memory: `new_user` builds a User, it does not enrol one. A user becomes
94    // part of the station when it is inserted into this file's list and the
95    // file is written.
96    let users_file = engine.users_file()?;
97    let file = users_file.as_property_object_file()?;
98    println!(
99        "
100Users file:"
101    );
102    println!("  path            {}", file.path()?);
103    println!(
104        "  users           {}",
105        users_file.user_list()?.get_num_elements()?
106    );
107    println!(
108        "  groups          {}",
109        users_file.user_group_list()?.get_num_elements()?
110    );
111    println!(
112        "  profiles        {}",
113        users_file.user_profile_list()?.get_num_elements()?
114    );
115
116    // Deliberately not run: it would edit the station this example is reading.
117    // The two calls a host makes to enrol the user built above are
118    //
119    //     users_file.user_list()?.set_num_elements(count + 1, 0)?;   // then fill
120    //     file.save_file_if_modified(false)?;                        // then write
121    //
122    // `false` matters: with `true` the engine puts a save dialog on screen, and
123    // a host with no operator would wait for an answer that never comes.
124    println!(
125        "
126  (this example does not write; enrolling a user would alter the station)"
127    );
128
129    Ok(())
130}
Source

pub fn get_nth_sub_property_name( &self, lookup_string: &str, index: i32, options: i32, ) -> Result<String, Error>

The name of the index-th sub-property (GetNthSubPropertyName).

§Errors

Error if the COM call fails or returns an unexpected type.

Examples found in repository?
examples/variables_manage.rs (line 171)
100fn show_station_globals(engine: &Engine) -> Result<(), rs_teststand::Error> {
101    // Station globals: shared by every sequence file, and written to disk.
102    let station_globals = engine.globals()?;
103    if !station_globals.exists("StationInfo", 0)? {
104        station_globals.new_sub_property(
105            "StationInfo",
106            PropValType::Container,
107            false,
108            "",
109            INSERT_IF_MISSING,
110        )?;
111    }
112    let station_info = station_globals.get_property_object("StationInfo", 0)?;
113
114    // A container holds mixed types, so one station record can carry the name,
115    // a count and a flag without three separate globals.
116    set_string(&station_info, "StationName", "STATION_01")?;
117    station_info.set_val_number("CalibrationIntervalDays", INSERT_IF_MISSING, 90.0)?;
118    station_info.set_val_bool("FixtureInstalled", INSERT_IF_MISSING, true)?;
119
120    // A 64-bit count. `SetValNumber` stores a double, which starts losing whole
121    // numbers past 2^53, so anything that counts for the life of a station wants
122    // the integer path instead.
123    station_info.set_val_integer64("UnitsTestedTotal", INSERT_IF_MISSING, 9_007_199_254_740_993)?;
124
125    // A nested container, because station data is rarely one level deep.
126    if !station_info.exists("LastCalibration", 0)? {
127        station_info.new_sub_property(
128            "LastCalibration",
129            PropValType::Container,
130            false,
131            "",
132            INSERT_IF_MISSING,
133        )?;
134    }
135    let calibration = station_info.get_property_object("LastCalibration", 0)?;
136    set_string(&calibration, "Technician", "R. Alvarez")?;
137    set_string(&calibration, "Date", "2026-06-14")?;
138
139    println!("StationGlobals.StationInfo:");
140    println!(
141        "  StationName             = '{}'",
142        station_info.get_val_string("StationName", 0)?
143    );
144    println!(
145        "  CalibrationIntervalDays = {}",
146        station_info.get_val_number("CalibrationIntervalDays", 0)?
147    );
148    println!(
149        "  FixtureInstalled        = {}",
150        station_info.get_val_bool("FixtureInstalled", 0)?
151    );
152    println!(
153        "  UnitsTestedTotal        = {}",
154        station_info.get_val_integer64("UnitsTestedTotal", 0)?
155    );
156    println!(
157        "  LastCalibration.Technician = '{}' on {}",
158        calibration.get_val_string("Technician", 0)?,
159        calibration.get_val_string("Date", 0)?
160    );
161
162    // Walk the container rather than naming each field, which is what a host
163    // does when it does not know the shape in advance.
164    println!(
165        "  walked, {} field(s):",
166        station_info.get_num_sub_properties("")?
167    );
168    for index in 0..station_info.get_num_sub_properties("")? {
169        println!(
170            "    {}",
171            station_info.get_nth_sub_property_name("", index, 0)?
172        );
173    }
174
175    // Station globals live in memory until this is called. Without it the values
176    // above are gone when the engine goes away, which is the difference between
177    // a station global and a file global.
178    // `false` means do not prompt if another process changed the file first.
179    // An example must never raise a dialog, and neither must a headless host.
180    engine.commit_globals_to_disk(false)?;
181    println!("  committed to disk");
182
183    Ok(())
184}
More examples
Hide additional examples
examples/users_manage.rs (line 74)
29fn main() -> Result<(), rs_teststand::Error> {
30    let engine = Engine::new()?;
31
32    // Passing no profile means the user inherits nothing.
33    let user = engine.new_user(None)?;
34    user.set_login_name("operator1")?;
35    user.set_full_name("Test Operator")?;
36    user.set_password("ts-secret")?;
37
38    println!("User: {} ({})", user.login_name()?, user.full_name()?);
39
40    // Check a credential without handling the stored value.
41    println!(
42        "  password 'ts-secret' valid: {}",
43        user.validate_password("ts-secret")?
44    );
45    println!(
46        "  password 'wrong' valid:     {}",
47        user.validate_password("wrong")?
48    );
49
50    println!("  privilege checks:");
51    report_privileges(
52        &user,
53        &[
54            UserPrivilege::Operate,
55            UserPrivilege::Execute,
56            UserPrivilege::Develop,
57            UserPrivilege::Debug,
58            UserPrivilege::EditUsers,
59        ],
60    )?;
61
62    // A privilege can also be named by its full path, which is how a leaf
63    // inside a category is reached.
64    println!(
65        "  Debug.RunSelectedTests:  {}",
66        user.has_privilege_named("Debug.RunSelectedTests")?
67    );
68
69    // The privilege tree is nested: categories hold the individual rights.
70    let privileges = user.privileges()?;
71    let count = privileges.get_num_sub_properties("")?;
72    println!("  privilege tree ({count} categories):");
73    for index in 0..count {
74        let name = privileges.get_nth_sub_property_name("", index, 0)?;
75        let members = privileges
76            .get_property_object(&name, 0)?
77            .get_num_sub_properties("")?;
78        println!("    {name:<12} {members} member(s)");
79    }
80
81    // The station itself, read-only.
82    println!("\nStation:");
83    match engine.current_user()? {
84        Some(current) => println!("  logged in as {}", current.login_name()?),
85        None => println!("  nobody logged in (usual when login is not required)"),
86    }
87    println!(
88        "  is 'operator1' a real account here? {}",
89        engine.user_name_exists("operator1")?
90    );
91
92    // Where accounts actually live. Everything above this point exists only in
93    // memory: `new_user` builds a User, it does not enrol one. A user becomes
94    // part of the station when it is inserted into this file's list and the
95    // file is written.
96    let users_file = engine.users_file()?;
97    let file = users_file.as_property_object_file()?;
98    println!(
99        "
100Users file:"
101    );
102    println!("  path            {}", file.path()?);
103    println!(
104        "  users           {}",
105        users_file.user_list()?.get_num_elements()?
106    );
107    println!(
108        "  groups          {}",
109        users_file.user_group_list()?.get_num_elements()?
110    );
111    println!(
112        "  profiles        {}",
113        users_file.user_profile_list()?.get_num_elements()?
114    );
115
116    // Deliberately not run: it would edit the station this example is reading.
117    // The two calls a host makes to enrol the user built above are
118    //
119    //     users_file.user_list()?.set_num_elements(count + 1, 0)?;   // then fill
120    //     file.save_file_if_modified(false)?;                        // then write
121    //
122    // `false` matters: with `true` the engine puts a save dialog on screen, and
123    // a host with no operator would wait for an answer that never comes.
124    println!(
125        "
126  (this example does not write; enrolling a user would alter the station)"
127    );
128
129    Ok(())
130}
Source

pub fn get_nth_sub_property( &self, lookup_string: &str, index: i32, options: i32, ) -> Result<Self, Error>

The index-th sub-property itself (GetNthSubProperty).

§Errors

Error if the COM call fails or returns an unexpected type.

Source

pub fn get_val_integer64( &self, lookup_string: &str, options: i32, ) -> Result<i64, Error>

Reads a signed 64-bit integer property (GetValInteger64).

The engine stores a number as one of three things, a double, a signed 64-bit integer, or an unsigned one, and the accessor must match. Use this when property_type reports PropertyRepresentation::Int64; get_val_number fails on such a property rather than converting.

§Errors

Error if the COM call fails or the property is not stored as a signed 64-bit integer.

Examples found in repository?
examples/variables_manage.rs (line 154)
100fn show_station_globals(engine: &Engine) -> Result<(), rs_teststand::Error> {
101    // Station globals: shared by every sequence file, and written to disk.
102    let station_globals = engine.globals()?;
103    if !station_globals.exists("StationInfo", 0)? {
104        station_globals.new_sub_property(
105            "StationInfo",
106            PropValType::Container,
107            false,
108            "",
109            INSERT_IF_MISSING,
110        )?;
111    }
112    let station_info = station_globals.get_property_object("StationInfo", 0)?;
113
114    // A container holds mixed types, so one station record can carry the name,
115    // a count and a flag without three separate globals.
116    set_string(&station_info, "StationName", "STATION_01")?;
117    station_info.set_val_number("CalibrationIntervalDays", INSERT_IF_MISSING, 90.0)?;
118    station_info.set_val_bool("FixtureInstalled", INSERT_IF_MISSING, true)?;
119
120    // A 64-bit count. `SetValNumber` stores a double, which starts losing whole
121    // numbers past 2^53, so anything that counts for the life of a station wants
122    // the integer path instead.
123    station_info.set_val_integer64("UnitsTestedTotal", INSERT_IF_MISSING, 9_007_199_254_740_993)?;
124
125    // A nested container, because station data is rarely one level deep.
126    if !station_info.exists("LastCalibration", 0)? {
127        station_info.new_sub_property(
128            "LastCalibration",
129            PropValType::Container,
130            false,
131            "",
132            INSERT_IF_MISSING,
133        )?;
134    }
135    let calibration = station_info.get_property_object("LastCalibration", 0)?;
136    set_string(&calibration, "Technician", "R. Alvarez")?;
137    set_string(&calibration, "Date", "2026-06-14")?;
138
139    println!("StationGlobals.StationInfo:");
140    println!(
141        "  StationName             = '{}'",
142        station_info.get_val_string("StationName", 0)?
143    );
144    println!(
145        "  CalibrationIntervalDays = {}",
146        station_info.get_val_number("CalibrationIntervalDays", 0)?
147    );
148    println!(
149        "  FixtureInstalled        = {}",
150        station_info.get_val_bool("FixtureInstalled", 0)?
151    );
152    println!(
153        "  UnitsTestedTotal        = {}",
154        station_info.get_val_integer64("UnitsTestedTotal", 0)?
155    );
156    println!(
157        "  LastCalibration.Technician = '{}' on {}",
158        calibration.get_val_string("Technician", 0)?,
159        calibration.get_val_string("Date", 0)?
160    );
161
162    // Walk the container rather than naming each field, which is what a host
163    // does when it does not know the shape in advance.
164    println!(
165        "  walked, {} field(s):",
166        station_info.get_num_sub_properties("")?
167    );
168    for index in 0..station_info.get_num_sub_properties("")? {
169        println!(
170            "    {}",
171            station_info.get_nth_sub_property_name("", index, 0)?
172        );
173    }
174
175    // Station globals live in memory until this is called. Without it the values
176    // above are gone when the engine goes away, which is the difference between
177    // a station global and a file global.
178    // `false` means do not prompt if another process changed the file first.
179    // An example must never raise a dialog, and neither must a headless host.
180    engine.commit_globals_to_disk(false)?;
181    println!("  committed to disk");
182
183    Ok(())
184}
Source

pub fn set_val_integer64( &self, lookup_string: &str, options: i32, value: i64, ) -> Result<(), Error>

Writes a signed 64-bit integer property (SetValInteger64).

§Errors

Error if the COM call fails.

Examples found in repository?
examples/variables_manage.rs (line 123)
100fn show_station_globals(engine: &Engine) -> Result<(), rs_teststand::Error> {
101    // Station globals: shared by every sequence file, and written to disk.
102    let station_globals = engine.globals()?;
103    if !station_globals.exists("StationInfo", 0)? {
104        station_globals.new_sub_property(
105            "StationInfo",
106            PropValType::Container,
107            false,
108            "",
109            INSERT_IF_MISSING,
110        )?;
111    }
112    let station_info = station_globals.get_property_object("StationInfo", 0)?;
113
114    // A container holds mixed types, so one station record can carry the name,
115    // a count and a flag without three separate globals.
116    set_string(&station_info, "StationName", "STATION_01")?;
117    station_info.set_val_number("CalibrationIntervalDays", INSERT_IF_MISSING, 90.0)?;
118    station_info.set_val_bool("FixtureInstalled", INSERT_IF_MISSING, true)?;
119
120    // A 64-bit count. `SetValNumber` stores a double, which starts losing whole
121    // numbers past 2^53, so anything that counts for the life of a station wants
122    // the integer path instead.
123    station_info.set_val_integer64("UnitsTestedTotal", INSERT_IF_MISSING, 9_007_199_254_740_993)?;
124
125    // A nested container, because station data is rarely one level deep.
126    if !station_info.exists("LastCalibration", 0)? {
127        station_info.new_sub_property(
128            "LastCalibration",
129            PropValType::Container,
130            false,
131            "",
132            INSERT_IF_MISSING,
133        )?;
134    }
135    let calibration = station_info.get_property_object("LastCalibration", 0)?;
136    set_string(&calibration, "Technician", "R. Alvarez")?;
137    set_string(&calibration, "Date", "2026-06-14")?;
138
139    println!("StationGlobals.StationInfo:");
140    println!(
141        "  StationName             = '{}'",
142        station_info.get_val_string("StationName", 0)?
143    );
144    println!(
145        "  CalibrationIntervalDays = {}",
146        station_info.get_val_number("CalibrationIntervalDays", 0)?
147    );
148    println!(
149        "  FixtureInstalled        = {}",
150        station_info.get_val_bool("FixtureInstalled", 0)?
151    );
152    println!(
153        "  UnitsTestedTotal        = {}",
154        station_info.get_val_integer64("UnitsTestedTotal", 0)?
155    );
156    println!(
157        "  LastCalibration.Technician = '{}' on {}",
158        calibration.get_val_string("Technician", 0)?,
159        calibration.get_val_string("Date", 0)?
160    );
161
162    // Walk the container rather than naming each field, which is what a host
163    // does when it does not know the shape in advance.
164    println!(
165        "  walked, {} field(s):",
166        station_info.get_num_sub_properties("")?
167    );
168    for index in 0..station_info.get_num_sub_properties("")? {
169        println!(
170            "    {}",
171            station_info.get_nth_sub_property_name("", index, 0)?
172        );
173    }
174
175    // Station globals live in memory until this is called. Without it the values
176    // above are gone when the engine goes away, which is the difference between
177    // a station global and a file global.
178    // `false` means do not prompt if another process changed the file first.
179    // An example must never raise a dialog, and neither must a headless host.
180    engine.commit_globals_to_disk(false)?;
181    println!("  committed to disk");
182
183    Ok(())
184}
Source

pub fn get_val_unsigned_integer64( &self, lookup_string: &str, options: i32, ) -> Result<u64, Error>

Reads an unsigned 64-bit integer property (GetValUnsignedInteger64).

Use this when the representation is UInt64. The value crosses the COM boundary as VT_UI8 and is returned with its bits intact, so the full unsigned range survives.

§Errors

Error if the COM call fails or the property is not stored as an unsigned 64-bit integer.

Source

pub fn set_val_unsigned_integer64( &self, lookup_string: &str, options: i32, value: u64, ) -> Result<(), Error>

Writes an unsigned 64-bit integer property (SetValUnsignedInteger64).

§Errors

Error if the COM call fails.

Source

pub fn numeric_format(&self) -> Result<String, Error>

The per-property numeric format string (PropertyObject.NumericFormat).

A printf-style format that decides how get_formatted_value renders a number, so the same stored value can display as decimal, hex, octal or binary. It is presentation only, the underlying number is unchanged.

Two departures from C: %b formats in binary, and a $ placed straight after the % strips trailing zeros after the decimal point. An empty string restores the default format.

§Errors

Error if the COM call fails or returns an unexpected type.

Source

pub fn set_numeric_format(&self, format: &str) -> Result<(), Error>

Sets the numeric format string (PropertyObject.NumericFormat).

§Errors

Error if the COM call fails.

Source

pub fn get_formatted_value( &self, lookup_string: &str, options: i32, format: &str, use_value_format_if_defined: bool, separator: &str, ) -> Result<String, Error>

Renders a property’s value as display text (GetFormattedValue).

format overrides the formatting for this call; pass an empty string to use the default. Set use_value_format_if_defined to honour the property’s own numeric_format instead. separator joins array elements.

Containers render as ... and an empty reference as Nothing, so the result is always displayable text rather than an error.

§Errors

Error if the COM call fails or returns an unexpected type.

Source

pub fn name(&self) -> Result<String, Error>

The object’s name (PropertyObject.Name).

A type definition must be named before it can be registered.

§Errors

Error if the COM call fails or returns an unexpected type.

Examples found in repository?
examples/step_insert_from_template.rs (line 40)
33fn describe_templates(engine: &Engine) -> Result<(), Error> {
34    let templates_file = engine.get_templates_file(GetTemplatesFileOptions::LOAD_IF_NOT_LOADED)?;
35    let root = templates_file
36        .data()?
37        .get_property_object("Root", PropertyOptions::NONE.bits())?;
38    for index in 0..root.get_num_elements()? {
39        let category = root.get_property_object_by_offset(index, PropertyOptions::NONE.bits())?;
40        if category.name()? == "Steps" {
41            println!(
42                "Step templates defined on this station: {}",
43                category.get_num_elements()?
44            );
45        }
46    }
47    Ok(())
48}
More examples
Hide additional examples
examples/template_manage_complex.rs (line 51)
40fn describe_station_templates(engine: &Engine) -> Result<(), Error> {
41    let templates_file = engine.get_templates_file(GetTemplatesFileOptions::LOAD_IF_NOT_LOADED)?;
42    let root = templates_file
43        .data()?
44        .get_property_object("Root", PropertyOptions::NONE.bits())?;
45
46    println!("Station templates file: {}", templates_file.path()?);
47    for index in 0..root.get_num_elements()? {
48        let category = root.get_property_object_by_offset(index, PropertyOptions::NONE.bits())?;
49        println!(
50            "  {}: {} template(s)",
51            category.name()?,
52            category.get_num_elements()?
53        );
54    }
55    Ok(())
56}
57
58/// An empty array container to keep prototypes in.
59fn new_template_group(engine: &Engine) -> Result<PropertyObject, Error> {
60    engine.new_property_object(
61        PropValType::Container,
62        true,
63        "",
64        PropertyOptions::NONE.bits(),
65    )
66}
67
68/// Appends a detached copy of a prototype to the group.
69///
70/// The copy matters: the group must not share the object the caller keeps
71/// editing, or adding a second template would change the first.
72fn append_template(group: &PropertyObject, template: &PropertyObject) -> Result<(), Error> {
73    let index = group.get_num_elements()?;
74    group.set_num_elements(index + 1, PropertyOptions::NONE.bits())?;
75    group.set_property_object(
76        &format!("[{index}]"),
77        PropertyOptions::NONE.bits(),
78        &template.clone_property("", PropertyOptions::NONE.bits())?,
79    )
80}
81
82/// Finds a prototype in the group by name.
83fn find_template(group: &PropertyObject, name: &str) -> Result<Option<PropertyObject>, Error> {
84    for index in 0..group.get_num_elements()? {
85        let candidate = group.get_property_object_by_offset(index, PropertyOptions::NONE.bits())?;
86        if candidate.name()? == name {
87            return Ok(Some(candidate));
88        }
89    }
90    Ok(None)
91}
92
93/// Turns "not found" into an error naming the template that is missing.
94fn require(found: Option<PropertyObject>, name: &'static str) -> Result<PropertyObject, Error> {
95    found.ok_or(Error::UnexpectedType {
96        expected: name,
97        actual: "no template of that name in the group",
98    })
99}
100
101/// A configured Statement step, taken as a property tree so it can be cloned.
102fn step_template(engine: &Engine) -> Result<PropertyObject, Error> {
103    let step = engine.new_step(NO_ADAPTER, "Statement")?;
104    step.set_name(STEP_TEMPLATE)?;
105    step.set_post_expression(r#"Locals.Result = "Hello from step template!""#)?;
106    step.as_property_object()
107}
108
109/// A whole sequence, one step included, as a single prototype.
110fn sequence_template(engine: &Engine) -> Result<PropertyObject, Error> {
111    let sequence = engine.new_sequence()?;
112    sequence.set_name(SEQUENCE_TEMPLATE)?;
113
114    let step = engine.new_step(NO_ADAPTER, "Statement")?;
115    step.set_name("Inside_Sequence_Template")?;
116    sequence.insert_step(&step, 0, StepGroup::Main)?;
117
118    sequence.as_property_object()
119}
120
121/// A string variable carrying its default value.
122fn variable_template(engine: &Engine) -> Result<PropertyObject, Error> {
123    let variable =
124        engine.new_property_object(PropValType::String, false, "", PropertyOptions::NONE.bits())?;
125    variable.set_name(VARIABLE_TEMPLATE)?;
126    variable.set_val_string("", PropertyOptions::NONE.bits(), "Template Variable Value")?;
127    Ok(variable)
128}
129
130/// Stamps one copy of each template into a file that is already on disk.
131fn apply_templates(
132    sequence_file: &SequenceFile,
133    step: &PropertyObject,
134    sequence: &PropertyObject,
135    variable: &PropertyObject,
136) -> Result<(), Error> {
137    let main_sequence = sequence_file.get_sequence_by_name(MAIN_SEQUENCE)?;
138
139    // Inserting returns the copy on the Step interface, so it can be renamed
140    // and re-identified straight away. Without a new step ID the copy would go
141    // on claiming the prototype's identity.
142    let inserted_step = main_sequence.insert_step_from_template(step, 0, StepGroup::Main)?;
143    inserted_step.set_name("Step From Template")?;
144    inserted_step.create_new_unique_step_id()?;
145    println!("  step:     {}", inserted_step.name()?);
146
147    let inserted_sequence = sequence_file.insert_sequence_from_template(sequence)?;
148    inserted_sequence.create_new_unique_step_ids()?;
149    println!(
150        "  sequence: {} ({} step(s))",
151        inserted_sequence.name()?,
152        inserted_sequence.get_num_steps(StepGroup::Main)?
153    );
154
155    // A variable is the easy case: Locals is a property tree, so a clone drops
156    // straight in under the template's own name.
157    let locals = main_sequence.locals()?;
158    locals.set_property_object(
159        &variable.name()?,
160        PropertyOptions::INSERT_IF_MISSING.bits(),
161        &variable.clone_property("", PropertyOptions::NONE.bits())?,
162    )?;
163    println!(
164        "  variable: {} = {:?}",
165        variable.name()?,
166        locals.get_val_string(&variable.name()?, PropertyOptions::NONE.bits())?
167    );
168
169    // A file that does not believe it changed will not write anything.
170    sequence_file
171        .as_property_object_file()?
172        .inc_change_count()?;
173    Ok(())
174}
examples/data_type_manage.rs (line 93)
86fn print_enumerators(definition: &PropertyObject) -> Result<(), rs_teststand::Error> {
87    let enumerators = definition.enumerators()?;
88    let strict = enumerators
89        .attributes()?
90        .get_val_bool(IS_STRICT_ATTRIBUTE, NO_OPTIONS)?;
91    println!(
92        "  {} v{} (strict={strict}):",
93        definition.name()?,
94        definition.type_version()?
95    );
96    for index in 0..enumerators.get_num_elements()? {
97        let element = enumerators.get_property_object_by_offset(index, NO_OPTIONS)?;
98        let value = element.get_val_number("", COERCE_TO_NUMBER)?;
99        println!(
100            "    {} -> {value}",
101            element.get_value_display_name("", NO_OPTIONS)?
102        );
103    }
104    Ok(())
105}
Source

pub fn set_name(&self, name: &str) -> Result<(), Error>

Sets the object’s name (PropertyObject.Name).

§Errors

Error if the COM call fails.

Examples found in repository?
examples/template_manage_complex.rs (line 125)
122fn variable_template(engine: &Engine) -> Result<PropertyObject, Error> {
123    let variable =
124        engine.new_property_object(PropValType::String, false, "", PropertyOptions::NONE.bits())?;
125    variable.set_name(VARIABLE_TEMPLATE)?;
126    variable.set_val_string("", PropertyOptions::NONE.bits(), "Template Variable Value")?;
127    Ok(variable)
128}
More examples
Hide additional examples
examples/data_type_manage.rs (line 34)
28fn build_multimeter_type(engine: &Engine) -> Result<PropertyObject, rs_teststand::Error> {
29    let data_type = engine.new_property_object(PropValType::Container, false, "", NO_OPTIONS)?;
30    data_type.set_val_number("Resolution", INSERT_IF_MISSING, 6.5)?;
31    data_type.set_val_bool("AutoZero", INSERT_IF_MISSING, false)?;
32    data_type.set_val_string("Mode", INSERT_IF_MISSING, "Voltage")?;
33    data_type.set_val_number("Range", INSERT_IF_MISSING, 100.0)?;
34    data_type.set_name("DigitalMultimeter")?;
35    Ok(data_type)
36}
37
38/// Builds the array `UpdateEnumerators` expects.
39///
40/// One container per enumerator, each carrying `EnumeratorName` and
41/// `EnumeratorValue`. Strictness is an attribute of the array, not a member of
42/// it, a strict enumeration refuses values outside the declared set.
43fn enumerator_array(
44    engine: &Engine,
45    named_values: &[(&str, f64)],
46    strict: bool,
47) -> Result<PropertyObject, rs_teststand::Error> {
48    let array = engine.new_property_object(PropValType::Container, true, "", NO_OPTIONS)?;
49    array.set_num_elements(i32::try_from(named_values.len()).unwrap_or(0), NO_OPTIONS)?;
50    for (index, (name, value)) in named_values.iter().enumerate() {
51        let element =
52            array.get_property_object_by_offset(i32::try_from(index).unwrap_or(0), NO_OPTIONS)?;
53        element.set_val_string("EnumeratorName", INSERT_IF_MISSING, name)?;
54        element.set_val_number("EnumeratorValue", INSERT_IF_MISSING, *value)?;
55    }
56    array
57        .attributes()?
58        .set_val_bool(IS_STRICT_ATTRIBUTE, INSERT_IF_MISSING, strict)?;
59    Ok(array)
60}
61
62/// Registers an enumeration, then returns its **registered** definition.
63///
64/// The distinction matters: enumerators can only be set on the definition the
65/// file holds, not on the loose object that was inserted.
66fn register_enum(
67    engine: &Engine,
68    types: &TypeUsageList,
69    name: &str,
70    named_values: &[(&str, f64)],
71    strict: bool,
72) -> Result<PropertyObject, rs_teststand::Error> {
73    let enum_type = engine.new_property_object(PropValType::Enum, false, "", NO_OPTIONS)?;
74    enum_type.set_name(name)?;
75    types.insert_type(
76        &enum_type,
77        types.num_types()?,
78        TypeCategory::CustomDataTypes,
79    )?;
80
81    let definition = types.get_type_definition(types.get_type_index(name)?)?;
82    definition.update_enumerators(&enumerator_array(engine, named_values, strict)?)?;
83    Ok(definition)
84}
Source

pub fn type_version(&self) -> Result<String, Error>

A type’s version, as major.minor.revision.build (PropertyObject.TypeVersion).

Which field is bumped carries meaning: raising the lowest field signals a change the engine can apply to existing instances silently, while raising a higher one marks the change as deliberate.

§Errors

Error if the COM call fails or returns an unexpected type.

Examples found in repository?
examples/data_type_manage.rs (line 94)
86fn print_enumerators(definition: &PropertyObject) -> Result<(), rs_teststand::Error> {
87    let enumerators = definition.enumerators()?;
88    let strict = enumerators
89        .attributes()?
90        .get_val_bool(IS_STRICT_ATTRIBUTE, NO_OPTIONS)?;
91    println!(
92        "  {} v{} (strict={strict}):",
93        definition.name()?,
94        definition.type_version()?
95    );
96    for index in 0..enumerators.get_num_elements()? {
97        let element = enumerators.get_property_object_by_offset(index, NO_OPTIONS)?;
98        let value = element.get_val_number("", COERCE_TO_NUMBER)?;
99        println!(
100            "    {} -> {value}",
101            element.get_value_display_name("", NO_OPTIONS)?
102        );
103    }
104    Ok(())
105}
106
107fn main() -> Result<(), Box<dyn std::error::Error>> {
108    let engine = Engine::new()?;
109    let sequence_file = engine.new_sequence_file()?;
110    let file = sequence_file.as_property_object_file()?;
111    let types = file.type_usage_list()?;
112
113    types.insert_type(
114        &build_multimeter_type(&engine)?,
115        types.num_types()?,
116        TypeCategory::CustomDataTypes,
117    )?;
118    let coupling = register_enum(
119        &engine,
120        &types,
121        "Coupling",
122        &[("AC", 0.0), ("DC", 1.0)],
123        true,
124    )?;
125
126    // A variable of the enum type, so the change below has an instance to update.
127    let main_sequence = sequence_file.get_sequence_by_name("MainSequence")?;
128    main_sequence.locals()?.new_sub_property(
129        "InputCoupling",
130        PropValType::NamedType,
131        false,
132        "Coupling",
133        INSERT_IF_MISSING,
134    )?;
135
136    println!(
137        "Registered custom data types ({} in file):",
138        types.num_types()?
139    );
140    print_enumerators(&coupling)?;
141
142    // Evolve it: add an enumerator and raise the version.
143    println!(
144        "\nCoupling version before update: {}",
145        coupling.type_version()?
146    );
147    coupling.update_enumerators(&enumerator_array(
148        &engine,
149        &[("AC", 0.0), ("DC", 1.0), ("GND", 2.0)],
150        true,
151    )?)?;
152
153    // Raising the lowest field signals a change the engine applies silently;
154    // raising a higher one marks it as deliberate.
155    let version = coupling.type_version()?;
156    let mut fields = version.split('.');
157    let major: u32 = fields.next().unwrap_or("0").parse().unwrap_or(0);
158    let minor: u32 = fields.next().unwrap_or("0").parse().unwrap_or(0);
159    coupling.set_type_version(&format!("{major}.{}.0.0", minor + 1))?;
160    println!(
161        "Coupling version after  update: {}",
162        coupling.type_version()?
163    );
164
165    println!("\nCoupling now defines (InputCoupling reflects this):");
166    print_enumerators(&coupling)?;
167
168    // Saving does nothing unless the file believes it changed.
169    file.inc_change_count()?;
170    let path = std::env::temp_dir().join("rs_teststand_with_custom_types.seq");
171    sequence_file.save(&path.to_string_lossy())?;
172    println!("\nSaved to {}", path.display());
173
174    engine.release_sequence_file_ex(sequence_file, NO_OPTIONS)?;
175    Ok(())
176}
Source

pub fn set_type_version(&self, version: &str) -> Result<(), Error>

Sets a type’s version (PropertyObject.TypeVersion).

§Errors

Error if the COM call fails.

Examples found in repository?
examples/data_type_manage.rs (line 159)
107fn main() -> Result<(), Box<dyn std::error::Error>> {
108    let engine = Engine::new()?;
109    let sequence_file = engine.new_sequence_file()?;
110    let file = sequence_file.as_property_object_file()?;
111    let types = file.type_usage_list()?;
112
113    types.insert_type(
114        &build_multimeter_type(&engine)?,
115        types.num_types()?,
116        TypeCategory::CustomDataTypes,
117    )?;
118    let coupling = register_enum(
119        &engine,
120        &types,
121        "Coupling",
122        &[("AC", 0.0), ("DC", 1.0)],
123        true,
124    )?;
125
126    // A variable of the enum type, so the change below has an instance to update.
127    let main_sequence = sequence_file.get_sequence_by_name("MainSequence")?;
128    main_sequence.locals()?.new_sub_property(
129        "InputCoupling",
130        PropValType::NamedType,
131        false,
132        "Coupling",
133        INSERT_IF_MISSING,
134    )?;
135
136    println!(
137        "Registered custom data types ({} in file):",
138        types.num_types()?
139    );
140    print_enumerators(&coupling)?;
141
142    // Evolve it: add an enumerator and raise the version.
143    println!(
144        "\nCoupling version before update: {}",
145        coupling.type_version()?
146    );
147    coupling.update_enumerators(&enumerator_array(
148        &engine,
149        &[("AC", 0.0), ("DC", 1.0), ("GND", 2.0)],
150        true,
151    )?)?;
152
153    // Raising the lowest field signals a change the engine applies silently;
154    // raising a higher one marks it as deliberate.
155    let version = coupling.type_version()?;
156    let mut fields = version.split('.');
157    let major: u32 = fields.next().unwrap_or("0").parse().unwrap_or(0);
158    let minor: u32 = fields.next().unwrap_or("0").parse().unwrap_or(0);
159    coupling.set_type_version(&format!("{major}.{}.0.0", minor + 1))?;
160    println!(
161        "Coupling version after  update: {}",
162        coupling.type_version()?
163    );
164
165    println!("\nCoupling now defines (InputCoupling reflects this):");
166    print_enumerators(&coupling)?;
167
168    // Saving does nothing unless the file believes it changed.
169    file.inc_change_count()?;
170    let path = std::env::temp_dir().join("rs_teststand_with_custom_types.seq");
171    sequence_file.save(&path.to_string_lossy())?;
172    println!("\nSaved to {}", path.display());
173
174    engine.release_sequence_file_ex(sequence_file, NO_OPTIONS)?;
175    Ok(())
176}
Source

pub fn attributes(&self) -> Result<Self, Error>

The object’s attributes (PropertyObject.Attributes).

A property tree of its own, used for metadata that is not part of the value, an enumeration’s strictness flag, for instance.

§Errors

Error if the COM call fails or returns an unexpected type.

Examples found in repository?
examples/data_type_manage.rs (line 57)
43fn enumerator_array(
44    engine: &Engine,
45    named_values: &[(&str, f64)],
46    strict: bool,
47) -> Result<PropertyObject, rs_teststand::Error> {
48    let array = engine.new_property_object(PropValType::Container, true, "", NO_OPTIONS)?;
49    array.set_num_elements(i32::try_from(named_values.len()).unwrap_or(0), NO_OPTIONS)?;
50    for (index, (name, value)) in named_values.iter().enumerate() {
51        let element =
52            array.get_property_object_by_offset(i32::try_from(index).unwrap_or(0), NO_OPTIONS)?;
53        element.set_val_string("EnumeratorName", INSERT_IF_MISSING, name)?;
54        element.set_val_number("EnumeratorValue", INSERT_IF_MISSING, *value)?;
55    }
56    array
57        .attributes()?
58        .set_val_bool(IS_STRICT_ATTRIBUTE, INSERT_IF_MISSING, strict)?;
59    Ok(array)
60}
61
62/// Registers an enumeration, then returns its **registered** definition.
63///
64/// The distinction matters: enumerators can only be set on the definition the
65/// file holds, not on the loose object that was inserted.
66fn register_enum(
67    engine: &Engine,
68    types: &TypeUsageList,
69    name: &str,
70    named_values: &[(&str, f64)],
71    strict: bool,
72) -> Result<PropertyObject, rs_teststand::Error> {
73    let enum_type = engine.new_property_object(PropValType::Enum, false, "", NO_OPTIONS)?;
74    enum_type.set_name(name)?;
75    types.insert_type(
76        &enum_type,
77        types.num_types()?,
78        TypeCategory::CustomDataTypes,
79    )?;
80
81    let definition = types.get_type_definition(types.get_type_index(name)?)?;
82    definition.update_enumerators(&enumerator_array(engine, named_values, strict)?)?;
83    Ok(definition)
84}
85
86fn print_enumerators(definition: &PropertyObject) -> Result<(), rs_teststand::Error> {
87    let enumerators = definition.enumerators()?;
88    let strict = enumerators
89        .attributes()?
90        .get_val_bool(IS_STRICT_ATTRIBUTE, NO_OPTIONS)?;
91    println!(
92        "  {} v{} (strict={strict}):",
93        definition.name()?,
94        definition.type_version()?
95    );
96    for index in 0..enumerators.get_num_elements()? {
97        let element = enumerators.get_property_object_by_offset(index, NO_OPTIONS)?;
98        let value = element.get_val_number("", COERCE_TO_NUMBER)?;
99        println!(
100            "    {} -> {value}",
101            element.get_value_display_name("", NO_OPTIONS)?
102        );
103    }
104    Ok(())
105}
Source

pub fn enumerators(&self) -> Result<Self, Error>

An enumeration’s enumerators (PropertyObject.Enumerators).

§Errors

Error if the COM call fails or returns an unexpected type.

Examples found in repository?
examples/data_type_manage.rs (line 87)
86fn print_enumerators(definition: &PropertyObject) -> Result<(), rs_teststand::Error> {
87    let enumerators = definition.enumerators()?;
88    let strict = enumerators
89        .attributes()?
90        .get_val_bool(IS_STRICT_ATTRIBUTE, NO_OPTIONS)?;
91    println!(
92        "  {} v{} (strict={strict}):",
93        definition.name()?,
94        definition.type_version()?
95    );
96    for index in 0..enumerators.get_num_elements()? {
97        let element = enumerators.get_property_object_by_offset(index, NO_OPTIONS)?;
98        let value = element.get_val_number("", COERCE_TO_NUMBER)?;
99        println!(
100            "    {} -> {value}",
101            element.get_value_display_name("", NO_OPTIONS)?
102        );
103    }
104    Ok(())
105}
Source

pub fn update_enumerators(&self, enumerators: &Self) -> Result<bool, Error>

Replaces an enumeration’s enumerators (UpdateEnumerators).

Expects an array of containers, each holding EnumeratorName and EnumeratorValue. Strictness rides on the array’s attributes rather than on an element.

This only has an effect on a registered type definition; calling it on the loose object that was inserted changes nothing. Every loaded instance of the type is updated.

§Errors

Error if the COM call fails or the argument is not a live object.

Examples found in repository?
examples/data_type_manage.rs (line 82)
66fn register_enum(
67    engine: &Engine,
68    types: &TypeUsageList,
69    name: &str,
70    named_values: &[(&str, f64)],
71    strict: bool,
72) -> Result<PropertyObject, rs_teststand::Error> {
73    let enum_type = engine.new_property_object(PropValType::Enum, false, "", NO_OPTIONS)?;
74    enum_type.set_name(name)?;
75    types.insert_type(
76        &enum_type,
77        types.num_types()?,
78        TypeCategory::CustomDataTypes,
79    )?;
80
81    let definition = types.get_type_definition(types.get_type_index(name)?)?;
82    definition.update_enumerators(&enumerator_array(engine, named_values, strict)?)?;
83    Ok(definition)
84}
85
86fn print_enumerators(definition: &PropertyObject) -> Result<(), rs_teststand::Error> {
87    let enumerators = definition.enumerators()?;
88    let strict = enumerators
89        .attributes()?
90        .get_val_bool(IS_STRICT_ATTRIBUTE, NO_OPTIONS)?;
91    println!(
92        "  {} v{} (strict={strict}):",
93        definition.name()?,
94        definition.type_version()?
95    );
96    for index in 0..enumerators.get_num_elements()? {
97        let element = enumerators.get_property_object_by_offset(index, NO_OPTIONS)?;
98        let value = element.get_val_number("", COERCE_TO_NUMBER)?;
99        println!(
100            "    {} -> {value}",
101            element.get_value_display_name("", NO_OPTIONS)?
102        );
103    }
104    Ok(())
105}
106
107fn main() -> Result<(), Box<dyn std::error::Error>> {
108    let engine = Engine::new()?;
109    let sequence_file = engine.new_sequence_file()?;
110    let file = sequence_file.as_property_object_file()?;
111    let types = file.type_usage_list()?;
112
113    types.insert_type(
114        &build_multimeter_type(&engine)?,
115        types.num_types()?,
116        TypeCategory::CustomDataTypes,
117    )?;
118    let coupling = register_enum(
119        &engine,
120        &types,
121        "Coupling",
122        &[("AC", 0.0), ("DC", 1.0)],
123        true,
124    )?;
125
126    // A variable of the enum type, so the change below has an instance to update.
127    let main_sequence = sequence_file.get_sequence_by_name("MainSequence")?;
128    main_sequence.locals()?.new_sub_property(
129        "InputCoupling",
130        PropValType::NamedType,
131        false,
132        "Coupling",
133        INSERT_IF_MISSING,
134    )?;
135
136    println!(
137        "Registered custom data types ({} in file):",
138        types.num_types()?
139    );
140    print_enumerators(&coupling)?;
141
142    // Evolve it: add an enumerator and raise the version.
143    println!(
144        "\nCoupling version before update: {}",
145        coupling.type_version()?
146    );
147    coupling.update_enumerators(&enumerator_array(
148        &engine,
149        &[("AC", 0.0), ("DC", 1.0), ("GND", 2.0)],
150        true,
151    )?)?;
152
153    // Raising the lowest field signals a change the engine applies silently;
154    // raising a higher one marks it as deliberate.
155    let version = coupling.type_version()?;
156    let mut fields = version.split('.');
157    let major: u32 = fields.next().unwrap_or("0").parse().unwrap_or(0);
158    let minor: u32 = fields.next().unwrap_or("0").parse().unwrap_or(0);
159    coupling.set_type_version(&format!("{major}.{}.0.0", minor + 1))?;
160    println!(
161        "Coupling version after  update: {}",
162        coupling.type_version()?
163    );
164
165    println!("\nCoupling now defines (InputCoupling reflects this):");
166    print_enumerators(&coupling)?;
167
168    // Saving does nothing unless the file believes it changed.
169    file.inc_change_count()?;
170    let path = std::env::temp_dir().join("rs_teststand_with_custom_types.seq");
171    sequence_file.save(&path.to_string_lossy())?;
172    println!("\nSaved to {}", path.display());
173
174    engine.release_sequence_file_ex(sequence_file, NO_OPTIONS)?;
175    Ok(())
176}
Source

pub fn get_value_display_name( &self, lookup_string: &str, options: i32, ) -> Result<String, Error>

The display name of a value (GetValueDisplayName).

For an enumeration this is the enumerator’s name rather than its number.

§Errors

Error if the COM call fails or returns an unexpected type.

Examples found in repository?
examples/data_type_manage.rs (line 101)
86fn print_enumerators(definition: &PropertyObject) -> Result<(), rs_teststand::Error> {
87    let enumerators = definition.enumerators()?;
88    let strict = enumerators
89        .attributes()?
90        .get_val_bool(IS_STRICT_ATTRIBUTE, NO_OPTIONS)?;
91    println!(
92        "  {} v{} (strict={strict}):",
93        definition.name()?,
94        definition.type_version()?
95    );
96    for index in 0..enumerators.get_num_elements()? {
97        let element = enumerators.get_property_object_by_offset(index, NO_OPTIONS)?;
98        let value = element.get_val_number("", COERCE_TO_NUMBER)?;
99        println!(
100            "    {} -> {value}",
101            element.get_value_display_name("", NO_OPTIONS)?
102        );
103    }
104    Ok(())
105}
Source

pub fn evaluate(&self, expression: &str) -> Result<Self, Error>

Evaluates an expression in this object’s context (Evaluate).

Superseded by evaluate_ex, which adds an options argument. This form is kept because it is the member available on engines from TestStand 2016, which the crate supports, a caller targeting the whole range can use it without a version check.

§Errors

Error if the expression is invalid or the COM call fails.

Source

pub fn evaluate_ex(&self, expression: &str, options: i32) -> Result<Self, Error>

Evaluates an expression in this object’s context (EvaluateEx).

The object is the scope: the expression can name this property’s subproperties directly. The result comes back as a PropertyObject holding whatever type the expression produced, so read it with the accessor that matches, or with to_value.

Evaluate is the obsolete form of this member; use this one.

§Errors

Error if the expression is invalid or the COM call fails.

Source

pub fn property_type(&self) -> Result<PropertyObjectType, Error>

This property’s type object (PropertyObject.Type).

§Errors

Error if the COM call fails or returns an unexpected type.

Source

pub fn get_type_display_string( &self, lookup_string: &str, options: i32, ) -> Result<String, Error>

A human-readable name for a property’s type (GetTypeDisplayString).

Obsolete in the engine; prefer property_type then display_string.

This is the in-only route to identifying a type. GetType reports the same thing in more detail but returns three of its five arguments by reference, which the dispatch seam does not yet support.

§Errors

Error if the COM call fails or returns an unexpected type.

Source

pub fn get_type_flags( &self, lookup_string: &str, options: i32, ) -> Result<PropertyValueTypeFlags, Error>

A property’s type as a flag set (GetTypeFlags).

§Errors

Error if the COM call fails or returns an unexpected type.

Source

pub fn get_property_object_by_offset( &self, offset: i32, options: i32, ) -> Result<Self, Error>

An array element by position (GetPropertyObjectByOffset).

§Errors

Error if the COM call fails or returns an unexpected type.

Examples found in repository?
examples/step_insert_from_template.rs (line 39)
33fn describe_templates(engine: &Engine) -> Result<(), Error> {
34    let templates_file = engine.get_templates_file(GetTemplatesFileOptions::LOAD_IF_NOT_LOADED)?;
35    let root = templates_file
36        .data()?
37        .get_property_object("Root", PropertyOptions::NONE.bits())?;
38    for index in 0..root.get_num_elements()? {
39        let category = root.get_property_object_by_offset(index, PropertyOptions::NONE.bits())?;
40        if category.name()? == "Steps" {
41            println!(
42                "Step templates defined on this station: {}",
43                category.get_num_elements()?
44            );
45        }
46    }
47    Ok(())
48}
More examples
Hide additional examples
examples/template_manage_complex.rs (line 48)
40fn describe_station_templates(engine: &Engine) -> Result<(), Error> {
41    let templates_file = engine.get_templates_file(GetTemplatesFileOptions::LOAD_IF_NOT_LOADED)?;
42    let root = templates_file
43        .data()?
44        .get_property_object("Root", PropertyOptions::NONE.bits())?;
45
46    println!("Station templates file: {}", templates_file.path()?);
47    for index in 0..root.get_num_elements()? {
48        let category = root.get_property_object_by_offset(index, PropertyOptions::NONE.bits())?;
49        println!(
50            "  {}: {} template(s)",
51            category.name()?,
52            category.get_num_elements()?
53        );
54    }
55    Ok(())
56}
57
58/// An empty array container to keep prototypes in.
59fn new_template_group(engine: &Engine) -> Result<PropertyObject, Error> {
60    engine.new_property_object(
61        PropValType::Container,
62        true,
63        "",
64        PropertyOptions::NONE.bits(),
65    )
66}
67
68/// Appends a detached copy of a prototype to the group.
69///
70/// The copy matters: the group must not share the object the caller keeps
71/// editing, or adding a second template would change the first.
72fn append_template(group: &PropertyObject, template: &PropertyObject) -> Result<(), Error> {
73    let index = group.get_num_elements()?;
74    group.set_num_elements(index + 1, PropertyOptions::NONE.bits())?;
75    group.set_property_object(
76        &format!("[{index}]"),
77        PropertyOptions::NONE.bits(),
78        &template.clone_property("", PropertyOptions::NONE.bits())?,
79    )
80}
81
82/// Finds a prototype in the group by name.
83fn find_template(group: &PropertyObject, name: &str) -> Result<Option<PropertyObject>, Error> {
84    for index in 0..group.get_num_elements()? {
85        let candidate = group.get_property_object_by_offset(index, PropertyOptions::NONE.bits())?;
86        if candidate.name()? == name {
87            return Ok(Some(candidate));
88        }
89    }
90    Ok(None)
91}
examples/data_type_manage.rs (line 52)
43fn enumerator_array(
44    engine: &Engine,
45    named_values: &[(&str, f64)],
46    strict: bool,
47) -> Result<PropertyObject, rs_teststand::Error> {
48    let array = engine.new_property_object(PropValType::Container, true, "", NO_OPTIONS)?;
49    array.set_num_elements(i32::try_from(named_values.len()).unwrap_or(0), NO_OPTIONS)?;
50    for (index, (name, value)) in named_values.iter().enumerate() {
51        let element =
52            array.get_property_object_by_offset(i32::try_from(index).unwrap_or(0), NO_OPTIONS)?;
53        element.set_val_string("EnumeratorName", INSERT_IF_MISSING, name)?;
54        element.set_val_number("EnumeratorValue", INSERT_IF_MISSING, *value)?;
55    }
56    array
57        .attributes()?
58        .set_val_bool(IS_STRICT_ATTRIBUTE, INSERT_IF_MISSING, strict)?;
59    Ok(array)
60}
61
62/// Registers an enumeration, then returns its **registered** definition.
63///
64/// The distinction matters: enumerators can only be set on the definition the
65/// file holds, not on the loose object that was inserted.
66fn register_enum(
67    engine: &Engine,
68    types: &TypeUsageList,
69    name: &str,
70    named_values: &[(&str, f64)],
71    strict: bool,
72) -> Result<PropertyObject, rs_teststand::Error> {
73    let enum_type = engine.new_property_object(PropValType::Enum, false, "", NO_OPTIONS)?;
74    enum_type.set_name(name)?;
75    types.insert_type(
76        &enum_type,
77        types.num_types()?,
78        TypeCategory::CustomDataTypes,
79    )?;
80
81    let definition = types.get_type_definition(types.get_type_index(name)?)?;
82    definition.update_enumerators(&enumerator_array(engine, named_values, strict)?)?;
83    Ok(definition)
84}
85
86fn print_enumerators(definition: &PropertyObject) -> Result<(), rs_teststand::Error> {
87    let enumerators = definition.enumerators()?;
88    let strict = enumerators
89        .attributes()?
90        .get_val_bool(IS_STRICT_ATTRIBUTE, NO_OPTIONS)?;
91    println!(
92        "  {} v{} (strict={strict}):",
93        definition.name()?,
94        definition.type_version()?
95    );
96    for index in 0..enumerators.get_num_elements()? {
97        let element = enumerators.get_property_object_by_offset(index, NO_OPTIONS)?;
98        let value = element.get_val_number("", COERCE_TO_NUMBER)?;
99        println!(
100            "    {} -> {value}",
101            element.get_value_display_name("", NO_OPTIONS)?
102        );
103    }
104    Ok(())
105}
examples/execution_run_subsequence.rs (line 126)
108fn run(engine: &Engine, sequence_file: &SequenceFile, entry_point: &str) -> Result<(), Error> {
109    let execution = engine.new_execution(sequence_file, entry_point, None, false, 0)?;
110    println!("\nRunning {entry_point}...");
111
112    if !wait_for_end(engine, RUN_DEADLINE)? {
113        println!("  did not finish within {RUN_DEADLINE:?}; terminating");
114        execution.terminate()?;
115        return Ok(());
116    }
117    println!("  status: {}", execution.result_status()?);
118
119    let results = execution.result_object()?;
120    if !results.exists("ResultList", none())? {
121        println!("  no results recorded");
122        return Ok(());
123    }
124    let result_list = results.get_property_object("ResultList", none())?;
125    for index in 0..result_list.get_num_elements()? {
126        let entry = result_list.get_property_object_by_offset(index, none())?;
127        println!(
128            "  {}: {}",
129            entry
130                .get_val_string("TS.StepName", none())
131                .unwrap_or_else(|_| "<unnamed>".to_owned()),
132            entry
133                .get_val_string("Status", none())
134                .unwrap_or_else(|_| "<no status>".to_owned())
135        );
136    }
137    Ok(())
138}
examples/execution_run_test_headless.rs (line 130)
114fn report(results: &rs_teststand::PropertyObject) -> Result<(), Error> {
115    if !results.exists("ResultList", none())? {
116        println!("No results recorded.");
117        return Ok(());
118    }
119    let result_list = results.get_property_object("ResultList", none())?;
120    let count = result_list.get_num_elements()?;
121    println!(
122        "\n{count} of {} defined test(s) recorded a result:",
123        TESTS.len()
124    );
125    if count < i32::try_from(TESTS.len()).unwrap_or(i32::MAX) {
126        println!("  (a failure ended the run before the rest could execute)");
127    }
128
129    for index in 0..count {
130        let entry = result_list.get_property_object_by_offset(index, none())?;
131        let name = entry
132            .get_val_string("TS.StepName", none())
133            .unwrap_or_else(|_| "<unnamed>".to_owned());
134        let status = entry
135            .get_val_string("Status", none())
136            .unwrap_or_else(|_| "<no status>".to_owned());
137        // Only test steps record a measurement; an Action has no Numeric.
138        match entry.get_val_number("Numeric", none()) {
139            Ok(measured) => println!("  {name}: {status} (measured {measured})"),
140            Err(_) => println!("  {name}: {status}"),
141        }
142    }
143    Ok(())
144}
Source

pub fn set_num_elements(&self, count: i32, options: i32) -> Result<(), Error>

Resizes an array property (SetNumElements).

§Errors

Error if the COM call fails.

Examples found in repository?
examples/template_manage_complex.rs (line 74)
72fn append_template(group: &PropertyObject, template: &PropertyObject) -> Result<(), Error> {
73    let index = group.get_num_elements()?;
74    group.set_num_elements(index + 1, PropertyOptions::NONE.bits())?;
75    group.set_property_object(
76        &format!("[{index}]"),
77        PropertyOptions::NONE.bits(),
78        &template.clone_property("", PropertyOptions::NONE.bits())?,
79    )
80}
More examples
Hide additional examples
examples/data_type_manage.rs (line 49)
43fn enumerator_array(
44    engine: &Engine,
45    named_values: &[(&str, f64)],
46    strict: bool,
47) -> Result<PropertyObject, rs_teststand::Error> {
48    let array = engine.new_property_object(PropValType::Container, true, "", NO_OPTIONS)?;
49    array.set_num_elements(i32::try_from(named_values.len()).unwrap_or(0), NO_OPTIONS)?;
50    for (index, (name, value)) in named_values.iter().enumerate() {
51        let element =
52            array.get_property_object_by_offset(i32::try_from(index).unwrap_or(0), NO_OPTIONS)?;
53        element.set_val_string("EnumeratorName", INSERT_IF_MISSING, name)?;
54        element.set_val_number("EnumeratorValue", INSERT_IF_MISSING, *value)?;
55    }
56    array
57        .attributes()?
58        .set_val_bool(IS_STRICT_ATTRIBUTE, INSERT_IF_MISSING, strict)?;
59    Ok(array)
60}
Source

pub fn get_num_elements(&self) -> Result<i32, Error>

The number of elements in an array property (GetNumElements).

§Errors

Error if the COM call fails or returns an unexpected type.

Examples found in repository?
examples/step_insert_from_template.rs (line 38)
33fn describe_templates(engine: &Engine) -> Result<(), Error> {
34    let templates_file = engine.get_templates_file(GetTemplatesFileOptions::LOAD_IF_NOT_LOADED)?;
35    let root = templates_file
36        .data()?
37        .get_property_object("Root", PropertyOptions::NONE.bits())?;
38    for index in 0..root.get_num_elements()? {
39        let category = root.get_property_object_by_offset(index, PropertyOptions::NONE.bits())?;
40        if category.name()? == "Steps" {
41            println!(
42                "Step templates defined on this station: {}",
43                category.get_num_elements()?
44            );
45        }
46    }
47    Ok(())
48}
More examples
Hide additional examples
examples/template_manage_complex.rs (line 47)
40fn describe_station_templates(engine: &Engine) -> Result<(), Error> {
41    let templates_file = engine.get_templates_file(GetTemplatesFileOptions::LOAD_IF_NOT_LOADED)?;
42    let root = templates_file
43        .data()?
44        .get_property_object("Root", PropertyOptions::NONE.bits())?;
45
46    println!("Station templates file: {}", templates_file.path()?);
47    for index in 0..root.get_num_elements()? {
48        let category = root.get_property_object_by_offset(index, PropertyOptions::NONE.bits())?;
49        println!(
50            "  {}: {} template(s)",
51            category.name()?,
52            category.get_num_elements()?
53        );
54    }
55    Ok(())
56}
57
58/// An empty array container to keep prototypes in.
59fn new_template_group(engine: &Engine) -> Result<PropertyObject, Error> {
60    engine.new_property_object(
61        PropValType::Container,
62        true,
63        "",
64        PropertyOptions::NONE.bits(),
65    )
66}
67
68/// Appends a detached copy of a prototype to the group.
69///
70/// The copy matters: the group must not share the object the caller keeps
71/// editing, or adding a second template would change the first.
72fn append_template(group: &PropertyObject, template: &PropertyObject) -> Result<(), Error> {
73    let index = group.get_num_elements()?;
74    group.set_num_elements(index + 1, PropertyOptions::NONE.bits())?;
75    group.set_property_object(
76        &format!("[{index}]"),
77        PropertyOptions::NONE.bits(),
78        &template.clone_property("", PropertyOptions::NONE.bits())?,
79    )
80}
81
82/// Finds a prototype in the group by name.
83fn find_template(group: &PropertyObject, name: &str) -> Result<Option<PropertyObject>, Error> {
84    for index in 0..group.get_num_elements()? {
85        let candidate = group.get_property_object_by_offset(index, PropertyOptions::NONE.bits())?;
86        if candidate.name()? == name {
87            return Ok(Some(candidate));
88        }
89    }
90    Ok(None)
91}
92
93/// Turns "not found" into an error naming the template that is missing.
94fn require(found: Option<PropertyObject>, name: &'static str) -> Result<PropertyObject, Error> {
95    found.ok_or(Error::UnexpectedType {
96        expected: name,
97        actual: "no template of that name in the group",
98    })
99}
100
101/// A configured Statement step, taken as a property tree so it can be cloned.
102fn step_template(engine: &Engine) -> Result<PropertyObject, Error> {
103    let step = engine.new_step(NO_ADAPTER, "Statement")?;
104    step.set_name(STEP_TEMPLATE)?;
105    step.set_post_expression(r#"Locals.Result = "Hello from step template!""#)?;
106    step.as_property_object()
107}
108
109/// A whole sequence, one step included, as a single prototype.
110fn sequence_template(engine: &Engine) -> Result<PropertyObject, Error> {
111    let sequence = engine.new_sequence()?;
112    sequence.set_name(SEQUENCE_TEMPLATE)?;
113
114    let step = engine.new_step(NO_ADAPTER, "Statement")?;
115    step.set_name("Inside_Sequence_Template")?;
116    sequence.insert_step(&step, 0, StepGroup::Main)?;
117
118    sequence.as_property_object()
119}
120
121/// A string variable carrying its default value.
122fn variable_template(engine: &Engine) -> Result<PropertyObject, Error> {
123    let variable =
124        engine.new_property_object(PropValType::String, false, "", PropertyOptions::NONE.bits())?;
125    variable.set_name(VARIABLE_TEMPLATE)?;
126    variable.set_val_string("", PropertyOptions::NONE.bits(), "Template Variable Value")?;
127    Ok(variable)
128}
129
130/// Stamps one copy of each template into a file that is already on disk.
131fn apply_templates(
132    sequence_file: &SequenceFile,
133    step: &PropertyObject,
134    sequence: &PropertyObject,
135    variable: &PropertyObject,
136) -> Result<(), Error> {
137    let main_sequence = sequence_file.get_sequence_by_name(MAIN_SEQUENCE)?;
138
139    // Inserting returns the copy on the Step interface, so it can be renamed
140    // and re-identified straight away. Without a new step ID the copy would go
141    // on claiming the prototype's identity.
142    let inserted_step = main_sequence.insert_step_from_template(step, 0, StepGroup::Main)?;
143    inserted_step.set_name("Step From Template")?;
144    inserted_step.create_new_unique_step_id()?;
145    println!("  step:     {}", inserted_step.name()?);
146
147    let inserted_sequence = sequence_file.insert_sequence_from_template(sequence)?;
148    inserted_sequence.create_new_unique_step_ids()?;
149    println!(
150        "  sequence: {} ({} step(s))",
151        inserted_sequence.name()?,
152        inserted_sequence.get_num_steps(StepGroup::Main)?
153    );
154
155    // A variable is the easy case: Locals is a property tree, so a clone drops
156    // straight in under the template's own name.
157    let locals = main_sequence.locals()?;
158    locals.set_property_object(
159        &variable.name()?,
160        PropertyOptions::INSERT_IF_MISSING.bits(),
161        &variable.clone_property("", PropertyOptions::NONE.bits())?,
162    )?;
163    println!(
164        "  variable: {} = {:?}",
165        variable.name()?,
166        locals.get_val_string(&variable.name()?, PropertyOptions::NONE.bits())?
167    );
168
169    // A file that does not believe it changed will not write anything.
170    sequence_file
171        .as_property_object_file()?
172        .inc_change_count()?;
173    Ok(())
174}
175
176fn main() -> Result<(), Box<dyn std::error::Error>> {
177    let engine = Engine::new()?;
178    describe_station_templates(&engine)?;
179
180    println!("\nBuilding an in-memory template group...");
181    let group = new_template_group(&engine)?;
182    append_template(&group, &step_template(&engine)?)?;
183    append_template(&group, &sequence_template(&engine)?)?;
184    append_template(&group, &variable_template(&engine)?)?;
185    println!("  {} template(s) stored", group.get_num_elements()?);
186
187    let step = require(find_template(&group, STEP_TEMPLATE)?, STEP_TEMPLATE)?;
188    let sequence = require(find_template(&group, SEQUENCE_TEMPLATE)?, SEQUENCE_TEMPLATE)?;
189    let variable = require(find_template(&group, VARIABLE_TEMPLATE)?, VARIABLE_TEMPLATE)?;
190
191    // Saving first, then reopening, is deliberate: templates are worth having
192    // because they are applied to files a program did not build in this run.
193    let path = std::env::temp_dir().join("rs_teststand_from_templates.seq");
194    let path = path.to_string_lossy().into_owned();
195    engine.new_sequence_file()?.save(&path)?;
196
197    println!("\nApplying templates to the saved file...");
198    let target = engine.get_sequence_file_ex(
199        &path,
200        GetSeqFileOptions::DO_NOT_RUN_LOAD_CALLBACK,
201        rs_teststand::ConflictHandler::Error,
202    )?;
203    apply_templates(&target, &step, &sequence, &variable)?;
204    target.save(&path)?;
205    println!("\nSaved to {path}");
206
207    engine.release_sequence_file_ex(target, PropertyOptions::NONE.bits())?;
208    Ok(())
209}
examples/data_type_manage.rs (line 96)
86fn print_enumerators(definition: &PropertyObject) -> Result<(), rs_teststand::Error> {
87    let enumerators = definition.enumerators()?;
88    let strict = enumerators
89        .attributes()?
90        .get_val_bool(IS_STRICT_ATTRIBUTE, NO_OPTIONS)?;
91    println!(
92        "  {} v{} (strict={strict}):",
93        definition.name()?,
94        definition.type_version()?
95    );
96    for index in 0..enumerators.get_num_elements()? {
97        let element = enumerators.get_property_object_by_offset(index, NO_OPTIONS)?;
98        let value = element.get_val_number("", COERCE_TO_NUMBER)?;
99        println!(
100            "    {} -> {value}",
101            element.get_value_display_name("", NO_OPTIONS)?
102        );
103    }
104    Ok(())
105}
examples/execution_run_subsequence.rs (line 125)
108fn run(engine: &Engine, sequence_file: &SequenceFile, entry_point: &str) -> Result<(), Error> {
109    let execution = engine.new_execution(sequence_file, entry_point, None, false, 0)?;
110    println!("\nRunning {entry_point}...");
111
112    if !wait_for_end(engine, RUN_DEADLINE)? {
113        println!("  did not finish within {RUN_DEADLINE:?}; terminating");
114        execution.terminate()?;
115        return Ok(());
116    }
117    println!("  status: {}", execution.result_status()?);
118
119    let results = execution.result_object()?;
120    if !results.exists("ResultList", none())? {
121        println!("  no results recorded");
122        return Ok(());
123    }
124    let result_list = results.get_property_object("ResultList", none())?;
125    for index in 0..result_list.get_num_elements()? {
126        let entry = result_list.get_property_object_by_offset(index, none())?;
127        println!(
128            "  {}: {}",
129            entry
130                .get_val_string("TS.StepName", none())
131                .unwrap_or_else(|_| "<unnamed>".to_owned()),
132            entry
133                .get_val_string("Status", none())
134                .unwrap_or_else(|_| "<no status>".to_owned())
135        );
136    }
137    Ok(())
138}
examples/execution_run_test_headless.rs (line 120)
114fn report(results: &rs_teststand::PropertyObject) -> Result<(), Error> {
115    if !results.exists("ResultList", none())? {
116        println!("No results recorded.");
117        return Ok(());
118    }
119    let result_list = results.get_property_object("ResultList", none())?;
120    let count = result_list.get_num_elements()?;
121    println!(
122        "\n{count} of {} defined test(s) recorded a result:",
123        TESTS.len()
124    );
125    if count < i32::try_from(TESTS.len()).unwrap_or(i32::MAX) {
126        println!("  (a failure ended the run before the rest could execute)");
127    }
128
129    for index in 0..count {
130        let entry = result_list.get_property_object_by_offset(index, none())?;
131        let name = entry
132            .get_val_string("TS.StepName", none())
133            .unwrap_or_else(|_| "<unnamed>".to_owned());
134        let status = entry
135            .get_val_string("Status", none())
136            .unwrap_or_else(|_| "<no status>".to_owned());
137        // Only test steps record a measurement; an Action has no Numeric.
138        match entry.get_val_number("Numeric", none()) {
139            Ok(measured) => println!("  {name}: {status} (measured {measured})"),
140            Err(_) => println!("  {name}: {status}"),
141        }
142    }
143    Ok(())
144}
examples/users_manage.rs (line 105)
29fn main() -> Result<(), rs_teststand::Error> {
30    let engine = Engine::new()?;
31
32    // Passing no profile means the user inherits nothing.
33    let user = engine.new_user(None)?;
34    user.set_login_name("operator1")?;
35    user.set_full_name("Test Operator")?;
36    user.set_password("ts-secret")?;
37
38    println!("User: {} ({})", user.login_name()?, user.full_name()?);
39
40    // Check a credential without handling the stored value.
41    println!(
42        "  password 'ts-secret' valid: {}",
43        user.validate_password("ts-secret")?
44    );
45    println!(
46        "  password 'wrong' valid:     {}",
47        user.validate_password("wrong")?
48    );
49
50    println!("  privilege checks:");
51    report_privileges(
52        &user,
53        &[
54            UserPrivilege::Operate,
55            UserPrivilege::Execute,
56            UserPrivilege::Develop,
57            UserPrivilege::Debug,
58            UserPrivilege::EditUsers,
59        ],
60    )?;
61
62    // A privilege can also be named by its full path, which is how a leaf
63    // inside a category is reached.
64    println!(
65        "  Debug.RunSelectedTests:  {}",
66        user.has_privilege_named("Debug.RunSelectedTests")?
67    );
68
69    // The privilege tree is nested: categories hold the individual rights.
70    let privileges = user.privileges()?;
71    let count = privileges.get_num_sub_properties("")?;
72    println!("  privilege tree ({count} categories):");
73    for index in 0..count {
74        let name = privileges.get_nth_sub_property_name("", index, 0)?;
75        let members = privileges
76            .get_property_object(&name, 0)?
77            .get_num_sub_properties("")?;
78        println!("    {name:<12} {members} member(s)");
79    }
80
81    // The station itself, read-only.
82    println!("\nStation:");
83    match engine.current_user()? {
84        Some(current) => println!("  logged in as {}", current.login_name()?),
85        None => println!("  nobody logged in (usual when login is not required)"),
86    }
87    println!(
88        "  is 'operator1' a real account here? {}",
89        engine.user_name_exists("operator1")?
90    );
91
92    // Where accounts actually live. Everything above this point exists only in
93    // memory: `new_user` builds a User, it does not enrol one. A user becomes
94    // part of the station when it is inserted into this file's list and the
95    // file is written.
96    let users_file = engine.users_file()?;
97    let file = users_file.as_property_object_file()?;
98    println!(
99        "
100Users file:"
101    );
102    println!("  path            {}", file.path()?);
103    println!(
104        "  users           {}",
105        users_file.user_list()?.get_num_elements()?
106    );
107    println!(
108        "  groups          {}",
109        users_file.user_group_list()?.get_num_elements()?
110    );
111    println!(
112        "  profiles        {}",
113        users_file.user_profile_list()?.get_num_elements()?
114    );
115
116    // Deliberately not run: it would edit the station this example is reading.
117    // The two calls a host makes to enrol the user built above are
118    //
119    //     users_file.user_list()?.set_num_elements(count + 1, 0)?;   // then fill
120    //     file.save_file_if_modified(false)?;                        // then write
121    //
122    // `false` matters: with `true` the engine puts a save dialog on screen, and
123    // a host with no operator would wait for an answer that never comes.
124    println!(
125        "
126  (this example does not write; enrolling a user would alter the station)"
127    );
128
129    Ok(())
130}
Source

pub fn get_property_object( &self, lookup_string: &str, options: i32, ) -> Result<Self, Error>

Retrieves a nested PropertyObject by lookup path (PropertyObject.GetPropertyObject).

§Errors

Error if the COM call fails or returns an unexpected type.

Examples found in repository?
examples/step_insert_from_template.rs (line 37)
33fn describe_templates(engine: &Engine) -> Result<(), Error> {
34    let templates_file = engine.get_templates_file(GetTemplatesFileOptions::LOAD_IF_NOT_LOADED)?;
35    let root = templates_file
36        .data()?
37        .get_property_object("Root", PropertyOptions::NONE.bits())?;
38    for index in 0..root.get_num_elements()? {
39        let category = root.get_property_object_by_offset(index, PropertyOptions::NONE.bits())?;
40        if category.name()? == "Steps" {
41            println!(
42                "Step templates defined on this station: {}",
43                category.get_num_elements()?
44            );
45        }
46    }
47    Ok(())
48}
More examples
Hide additional examples
examples/template_manage_complex.rs (line 44)
40fn describe_station_templates(engine: &Engine) -> Result<(), Error> {
41    let templates_file = engine.get_templates_file(GetTemplatesFileOptions::LOAD_IF_NOT_LOADED)?;
42    let root = templates_file
43        .data()?
44        .get_property_object("Root", PropertyOptions::NONE.bits())?;
45
46    println!("Station templates file: {}", templates_file.path()?);
47    for index in 0..root.get_num_elements()? {
48        let category = root.get_property_object_by_offset(index, PropertyOptions::NONE.bits())?;
49        println!(
50            "  {}: {} template(s)",
51            category.name()?,
52            category.get_num_elements()?
53        );
54    }
55    Ok(())
56}
examples/execution_run_subsequence.rs (line 124)
108fn run(engine: &Engine, sequence_file: &SequenceFile, entry_point: &str) -> Result<(), Error> {
109    let execution = engine.new_execution(sequence_file, entry_point, None, false, 0)?;
110    println!("\nRunning {entry_point}...");
111
112    if !wait_for_end(engine, RUN_DEADLINE)? {
113        println!("  did not finish within {RUN_DEADLINE:?}; terminating");
114        execution.terminate()?;
115        return Ok(());
116    }
117    println!("  status: {}", execution.result_status()?);
118
119    let results = execution.result_object()?;
120    if !results.exists("ResultList", none())? {
121        println!("  no results recorded");
122        return Ok(());
123    }
124    let result_list = results.get_property_object("ResultList", none())?;
125    for index in 0..result_list.get_num_elements()? {
126        let entry = result_list.get_property_object_by_offset(index, none())?;
127        println!(
128            "  {}: {}",
129            entry
130                .get_val_string("TS.StepName", none())
131                .unwrap_or_else(|_| "<unnamed>".to_owned()),
132            entry
133                .get_val_string("Status", none())
134                .unwrap_or_else(|_| "<no status>".to_owned())
135        );
136    }
137    Ok(())
138}
examples/execution_run_test_headless.rs (line 119)
114fn report(results: &rs_teststand::PropertyObject) -> Result<(), Error> {
115    if !results.exists("ResultList", none())? {
116        println!("No results recorded.");
117        return Ok(());
118    }
119    let result_list = results.get_property_object("ResultList", none())?;
120    let count = result_list.get_num_elements()?;
121    println!(
122        "\n{count} of {} defined test(s) recorded a result:",
123        TESTS.len()
124    );
125    if count < i32::try_from(TESTS.len()).unwrap_or(i32::MAX) {
126        println!("  (a failure ended the run before the rest could execute)");
127    }
128
129    for index in 0..count {
130        let entry = result_list.get_property_object_by_offset(index, none())?;
131        let name = entry
132            .get_val_string("TS.StepName", none())
133            .unwrap_or_else(|_| "<unnamed>".to_owned());
134        let status = entry
135            .get_val_string("Status", none())
136            .unwrap_or_else(|_| "<no status>".to_owned());
137        // Only test steps record a measurement; an Action has no Numeric.
138        match entry.get_val_number("Numeric", none()) {
139            Ok(measured) => println!("  {name}: {status} (measured {measured})"),
140            Err(_) => println!("  {name}: {status}"),
141        }
142    }
143    Ok(())
144}
examples/variables_manage.rs (line 112)
100fn show_station_globals(engine: &Engine) -> Result<(), rs_teststand::Error> {
101    // Station globals: shared by every sequence file, and written to disk.
102    let station_globals = engine.globals()?;
103    if !station_globals.exists("StationInfo", 0)? {
104        station_globals.new_sub_property(
105            "StationInfo",
106            PropValType::Container,
107            false,
108            "",
109            INSERT_IF_MISSING,
110        )?;
111    }
112    let station_info = station_globals.get_property_object("StationInfo", 0)?;
113
114    // A container holds mixed types, so one station record can carry the name,
115    // a count and a flag without three separate globals.
116    set_string(&station_info, "StationName", "STATION_01")?;
117    station_info.set_val_number("CalibrationIntervalDays", INSERT_IF_MISSING, 90.0)?;
118    station_info.set_val_bool("FixtureInstalled", INSERT_IF_MISSING, true)?;
119
120    // A 64-bit count. `SetValNumber` stores a double, which starts losing whole
121    // numbers past 2^53, so anything that counts for the life of a station wants
122    // the integer path instead.
123    station_info.set_val_integer64("UnitsTestedTotal", INSERT_IF_MISSING, 9_007_199_254_740_993)?;
124
125    // A nested container, because station data is rarely one level deep.
126    if !station_info.exists("LastCalibration", 0)? {
127        station_info.new_sub_property(
128            "LastCalibration",
129            PropValType::Container,
130            false,
131            "",
132            INSERT_IF_MISSING,
133        )?;
134    }
135    let calibration = station_info.get_property_object("LastCalibration", 0)?;
136    set_string(&calibration, "Technician", "R. Alvarez")?;
137    set_string(&calibration, "Date", "2026-06-14")?;
138
139    println!("StationGlobals.StationInfo:");
140    println!(
141        "  StationName             = '{}'",
142        station_info.get_val_string("StationName", 0)?
143    );
144    println!(
145        "  CalibrationIntervalDays = {}",
146        station_info.get_val_number("CalibrationIntervalDays", 0)?
147    );
148    println!(
149        "  FixtureInstalled        = {}",
150        station_info.get_val_bool("FixtureInstalled", 0)?
151    );
152    println!(
153        "  UnitsTestedTotal        = {}",
154        station_info.get_val_integer64("UnitsTestedTotal", 0)?
155    );
156    println!(
157        "  LastCalibration.Technician = '{}' on {}",
158        calibration.get_val_string("Technician", 0)?,
159        calibration.get_val_string("Date", 0)?
160    );
161
162    // Walk the container rather than naming each field, which is what a host
163    // does when it does not know the shape in advance.
164    println!(
165        "  walked, {} field(s):",
166        station_info.get_num_sub_properties("")?
167    );
168    for index in 0..station_info.get_num_sub_properties("")? {
169        println!(
170            "    {}",
171            station_info.get_nth_sub_property_name("", index, 0)?
172        );
173    }
174
175    // Station globals live in memory until this is called. Without it the values
176    // above are gone when the engine goes away, which is the difference between
177    // a station global and a file global.
178    // `false` means do not prompt if another process changed the file first.
179    // An example must never raise a dialog, and neither must a headless host.
180    engine.commit_globals_to_disk(false)?;
181    println!("  committed to disk");
182
183    Ok(())
184}
examples/users_manage.rs (line 76)
29fn main() -> Result<(), rs_teststand::Error> {
30    let engine = Engine::new()?;
31
32    // Passing no profile means the user inherits nothing.
33    let user = engine.new_user(None)?;
34    user.set_login_name("operator1")?;
35    user.set_full_name("Test Operator")?;
36    user.set_password("ts-secret")?;
37
38    println!("User: {} ({})", user.login_name()?, user.full_name()?);
39
40    // Check a credential without handling the stored value.
41    println!(
42        "  password 'ts-secret' valid: {}",
43        user.validate_password("ts-secret")?
44    );
45    println!(
46        "  password 'wrong' valid:     {}",
47        user.validate_password("wrong")?
48    );
49
50    println!("  privilege checks:");
51    report_privileges(
52        &user,
53        &[
54            UserPrivilege::Operate,
55            UserPrivilege::Execute,
56            UserPrivilege::Develop,
57            UserPrivilege::Debug,
58            UserPrivilege::EditUsers,
59        ],
60    )?;
61
62    // A privilege can also be named by its full path, which is how a leaf
63    // inside a category is reached.
64    println!(
65        "  Debug.RunSelectedTests:  {}",
66        user.has_privilege_named("Debug.RunSelectedTests")?
67    );
68
69    // The privilege tree is nested: categories hold the individual rights.
70    let privileges = user.privileges()?;
71    let count = privileges.get_num_sub_properties("")?;
72    println!("  privilege tree ({count} categories):");
73    for index in 0..count {
74        let name = privileges.get_nth_sub_property_name("", index, 0)?;
75        let members = privileges
76            .get_property_object(&name, 0)?
77            .get_num_sub_properties("")?;
78        println!("    {name:<12} {members} member(s)");
79    }
80
81    // The station itself, read-only.
82    println!("\nStation:");
83    match engine.current_user()? {
84        Some(current) => println!("  logged in as {}", current.login_name()?),
85        None => println!("  nobody logged in (usual when login is not required)"),
86    }
87    println!(
88        "  is 'operator1' a real account here? {}",
89        engine.user_name_exists("operator1")?
90    );
91
92    // Where accounts actually live. Everything above this point exists only in
93    // memory: `new_user` builds a User, it does not enrol one. A user becomes
94    // part of the station when it is inserted into this file's list and the
95    // file is written.
96    let users_file = engine.users_file()?;
97    let file = users_file.as_property_object_file()?;
98    println!(
99        "
100Users file:"
101    );
102    println!("  path            {}", file.path()?);
103    println!(
104        "  users           {}",
105        users_file.user_list()?.get_num_elements()?
106    );
107    println!(
108        "  groups          {}",
109        users_file.user_group_list()?.get_num_elements()?
110    );
111    println!(
112        "  profiles        {}",
113        users_file.user_profile_list()?.get_num_elements()?
114    );
115
116    // Deliberately not run: it would edit the station this example is reading.
117    // The two calls a host makes to enrol the user built above are
118    //
119    //     users_file.user_list()?.set_num_elements(count + 1, 0)?;   // then fill
120    //     file.save_file_if_modified(false)?;                        // then write
121    //
122    // `false` matters: with `true` the engine puts a save dialog on screen, and
123    // a host with no operator would wait for an answer that never comes.
124    println!(
125        "
126  (this example does not write; enrolling a user would alter the station)"
127    );
128
129    Ok(())
130}
Source

pub fn set_property_object( &self, lookup_string: &str, options: i32, property_object_value: &Self, ) -> Result<(), Error>

Attaches a nested PropertyObject by lookup path (PropertyObject.SetPropertyObject).

§Errors

Error if the COM call fails.

Examples found in repository?
examples/template_manage_complex.rs (lines 75-79)
72fn append_template(group: &PropertyObject, template: &PropertyObject) -> Result<(), Error> {
73    let index = group.get_num_elements()?;
74    group.set_num_elements(index + 1, PropertyOptions::NONE.bits())?;
75    group.set_property_object(
76        &format!("[{index}]"),
77        PropertyOptions::NONE.bits(),
78        &template.clone_property("", PropertyOptions::NONE.bits())?,
79    )
80}
81
82/// Finds a prototype in the group by name.
83fn find_template(group: &PropertyObject, name: &str) -> Result<Option<PropertyObject>, Error> {
84    for index in 0..group.get_num_elements()? {
85        let candidate = group.get_property_object_by_offset(index, PropertyOptions::NONE.bits())?;
86        if candidate.name()? == name {
87            return Ok(Some(candidate));
88        }
89    }
90    Ok(None)
91}
92
93/// Turns "not found" into an error naming the template that is missing.
94fn require(found: Option<PropertyObject>, name: &'static str) -> Result<PropertyObject, Error> {
95    found.ok_or(Error::UnexpectedType {
96        expected: name,
97        actual: "no template of that name in the group",
98    })
99}
100
101/// A configured Statement step, taken as a property tree so it can be cloned.
102fn step_template(engine: &Engine) -> Result<PropertyObject, Error> {
103    let step = engine.new_step(NO_ADAPTER, "Statement")?;
104    step.set_name(STEP_TEMPLATE)?;
105    step.set_post_expression(r#"Locals.Result = "Hello from step template!""#)?;
106    step.as_property_object()
107}
108
109/// A whole sequence, one step included, as a single prototype.
110fn sequence_template(engine: &Engine) -> Result<PropertyObject, Error> {
111    let sequence = engine.new_sequence()?;
112    sequence.set_name(SEQUENCE_TEMPLATE)?;
113
114    let step = engine.new_step(NO_ADAPTER, "Statement")?;
115    step.set_name("Inside_Sequence_Template")?;
116    sequence.insert_step(&step, 0, StepGroup::Main)?;
117
118    sequence.as_property_object()
119}
120
121/// A string variable carrying its default value.
122fn variable_template(engine: &Engine) -> Result<PropertyObject, Error> {
123    let variable =
124        engine.new_property_object(PropValType::String, false, "", PropertyOptions::NONE.bits())?;
125    variable.set_name(VARIABLE_TEMPLATE)?;
126    variable.set_val_string("", PropertyOptions::NONE.bits(), "Template Variable Value")?;
127    Ok(variable)
128}
129
130/// Stamps one copy of each template into a file that is already on disk.
131fn apply_templates(
132    sequence_file: &SequenceFile,
133    step: &PropertyObject,
134    sequence: &PropertyObject,
135    variable: &PropertyObject,
136) -> Result<(), Error> {
137    let main_sequence = sequence_file.get_sequence_by_name(MAIN_SEQUENCE)?;
138
139    // Inserting returns the copy on the Step interface, so it can be renamed
140    // and re-identified straight away. Without a new step ID the copy would go
141    // on claiming the prototype's identity.
142    let inserted_step = main_sequence.insert_step_from_template(step, 0, StepGroup::Main)?;
143    inserted_step.set_name("Step From Template")?;
144    inserted_step.create_new_unique_step_id()?;
145    println!("  step:     {}", inserted_step.name()?);
146
147    let inserted_sequence = sequence_file.insert_sequence_from_template(sequence)?;
148    inserted_sequence.create_new_unique_step_ids()?;
149    println!(
150        "  sequence: {} ({} step(s))",
151        inserted_sequence.name()?,
152        inserted_sequence.get_num_steps(StepGroup::Main)?
153    );
154
155    // A variable is the easy case: Locals is a property tree, so a clone drops
156    // straight in under the template's own name.
157    let locals = main_sequence.locals()?;
158    locals.set_property_object(
159        &variable.name()?,
160        PropertyOptions::INSERT_IF_MISSING.bits(),
161        &variable.clone_property("", PropertyOptions::NONE.bits())?,
162    )?;
163    println!(
164        "  variable: {} = {:?}",
165        variable.name()?,
166        locals.get_val_string(&variable.name()?, PropertyOptions::NONE.bits())?
167    );
168
169    // A file that does not believe it changed will not write anything.
170    sequence_file
171        .as_property_object_file()?
172        .inc_change_count()?;
173    Ok(())
174}
More examples
Hide additional examples
examples/variables_manage.rs (line 64)
38fn temporary_variable_lifecycle(container: &PropertyObject) -> Result<(), rs_teststand::Error> {
39    let name = "TempScratch";
40    let clone_name = "TempScratchCopy";
41    for stale in [name, clone_name] {
42        if container.exists(stale, 0)? {
43            container.delete_sub_property(stale, 0)?;
44        }
45    }
46
47    container.new_sub_property(name, PropValType::String, false, "", INSERT_IF_MISSING)?;
48    container.set_val_string(name, 0, "scratch")?;
49    println!(
50        "  created {name} (String) = '{}'",
51        container.get_val_string(name, 0)?
52    );
53
54    container.delete_sub_property(name, 0)?;
55    container.new_sub_property(name, PropValType::Number, false, "", INSERT_IF_MISSING)?;
56    container.set_val_number(name, 0, 42.0)?;
57    println!(
58        "  retyped {name} -> Number = {}",
59        container.get_val_number(name, 0)?
60    );
61
62    // clone copies value and type; set_property_object attaches it under a new name.
63    let copy = container.clone_property(name, 0)?;
64    container.set_property_object(clone_name, INSERT_IF_MISSING, &copy)?;
65    println!(
66        "  cloned  {name} -> {clone_name} = {}",
67        container.get_val_number(clone_name, 0)?
68    );
69
70    container.delete_sub_property(clone_name, 0)?;
71    container.delete_sub_property(name, 0)?;
72    println!(
73        "  removed both: {name} exists={}, {clone_name} exists={}",
74        container.exists(name, 0)?,
75        container.exists(clone_name, 0)?
76    );
77    Ok(())
78}
Source

pub fn new_sub_property( &self, lookup_string: &str, value_type: PropValType, as_array: bool, type_name: &str, options: i32, ) -> Result<(), Error>

Creates a new sub-property (PropertyObject.NewSubProperty).

§Errors

Error if the COM call fails.

Examples found in repository?
examples/variables_manage.rs (line 29)
23fn set_string(
24    container: &PropertyObject,
25    name: &str,
26    value: &str,
27) -> Result<(), rs_teststand::Error> {
28    if !container.exists(name, 0)? {
29        container.new_sub_property(name, PropValType::String, false, "", INSERT_IF_MISSING)?;
30    }
31    container.set_val_string(name, 0, value)
32}
33
34/// Creates a variable, retypes it, clones it, then removes both.
35///
36/// The type is fixed at creation, so each "retype" is a delete followed by a
37/// fresh create, the same thing the sequence editor does behind the scenes.
38fn temporary_variable_lifecycle(container: &PropertyObject) -> Result<(), rs_teststand::Error> {
39    let name = "TempScratch";
40    let clone_name = "TempScratchCopy";
41    for stale in [name, clone_name] {
42        if container.exists(stale, 0)? {
43            container.delete_sub_property(stale, 0)?;
44        }
45    }
46
47    container.new_sub_property(name, PropValType::String, false, "", INSERT_IF_MISSING)?;
48    container.set_val_string(name, 0, "scratch")?;
49    println!(
50        "  created {name} (String) = '{}'",
51        container.get_val_string(name, 0)?
52    );
53
54    container.delete_sub_property(name, 0)?;
55    container.new_sub_property(name, PropValType::Number, false, "", INSERT_IF_MISSING)?;
56    container.set_val_number(name, 0, 42.0)?;
57    println!(
58        "  retyped {name} -> Number = {}",
59        container.get_val_number(name, 0)?
60    );
61
62    // clone copies value and type; set_property_object attaches it under a new name.
63    let copy = container.clone_property(name, 0)?;
64    container.set_property_object(clone_name, INSERT_IF_MISSING, &copy)?;
65    println!(
66        "  cloned  {name} -> {clone_name} = {}",
67        container.get_val_number(clone_name, 0)?
68    );
69
70    container.delete_sub_property(clone_name, 0)?;
71    container.delete_sub_property(name, 0)?;
72    println!(
73        "  removed both: {name} exists={}, {clone_name} exists={}",
74        container.exists(name, 0)?,
75        container.exists(clone_name, 0)?
76    );
77    Ok(())
78}
79
80/// Opens a sequence file if one was named on the command line.
81///
82/// Locals, parameters and file globals all need a real file; without one the
83/// example still demonstrates station globals.
84fn open_sequence_file(engine: &Engine) -> Result<Option<SequenceFile>, rs_teststand::Error> {
85    let Some(path) = std::env::args().nth(1) else {
86        return Ok(None);
87    };
88    let file = engine.get_sequence_file_ex(
89        &path,
90        GetSeqFileOptions::DO_NOT_RUN_LOAD_CALLBACK,
91        ConflictHandler::Error,
92    )?;
93    Ok(Some(file))
94}
95
96/// Everything that lives on the station rather than in a file.
97///
98/// Separated because it is the one scope that outlives the process, so it is
99/// the one worth reading on its own.
100fn show_station_globals(engine: &Engine) -> Result<(), rs_teststand::Error> {
101    // Station globals: shared by every sequence file, and written to disk.
102    let station_globals = engine.globals()?;
103    if !station_globals.exists("StationInfo", 0)? {
104        station_globals.new_sub_property(
105            "StationInfo",
106            PropValType::Container,
107            false,
108            "",
109            INSERT_IF_MISSING,
110        )?;
111    }
112    let station_info = station_globals.get_property_object("StationInfo", 0)?;
113
114    // A container holds mixed types, so one station record can carry the name,
115    // a count and a flag without three separate globals.
116    set_string(&station_info, "StationName", "STATION_01")?;
117    station_info.set_val_number("CalibrationIntervalDays", INSERT_IF_MISSING, 90.0)?;
118    station_info.set_val_bool("FixtureInstalled", INSERT_IF_MISSING, true)?;
119
120    // A 64-bit count. `SetValNumber` stores a double, which starts losing whole
121    // numbers past 2^53, so anything that counts for the life of a station wants
122    // the integer path instead.
123    station_info.set_val_integer64("UnitsTestedTotal", INSERT_IF_MISSING, 9_007_199_254_740_993)?;
124
125    // A nested container, because station data is rarely one level deep.
126    if !station_info.exists("LastCalibration", 0)? {
127        station_info.new_sub_property(
128            "LastCalibration",
129            PropValType::Container,
130            false,
131            "",
132            INSERT_IF_MISSING,
133        )?;
134    }
135    let calibration = station_info.get_property_object("LastCalibration", 0)?;
136    set_string(&calibration, "Technician", "R. Alvarez")?;
137    set_string(&calibration, "Date", "2026-06-14")?;
138
139    println!("StationGlobals.StationInfo:");
140    println!(
141        "  StationName             = '{}'",
142        station_info.get_val_string("StationName", 0)?
143    );
144    println!(
145        "  CalibrationIntervalDays = {}",
146        station_info.get_val_number("CalibrationIntervalDays", 0)?
147    );
148    println!(
149        "  FixtureInstalled        = {}",
150        station_info.get_val_bool("FixtureInstalled", 0)?
151    );
152    println!(
153        "  UnitsTestedTotal        = {}",
154        station_info.get_val_integer64("UnitsTestedTotal", 0)?
155    );
156    println!(
157        "  LastCalibration.Technician = '{}' on {}",
158        calibration.get_val_string("Technician", 0)?,
159        calibration.get_val_string("Date", 0)?
160    );
161
162    // Walk the container rather than naming each field, which is what a host
163    // does when it does not know the shape in advance.
164    println!(
165        "  walked, {} field(s):",
166        station_info.get_num_sub_properties("")?
167    );
168    for index in 0..station_info.get_num_sub_properties("")? {
169        println!(
170            "    {}",
171            station_info.get_nth_sub_property_name("", index, 0)?
172        );
173    }
174
175    // Station globals live in memory until this is called. Without it the values
176    // above are gone when the engine goes away, which is the difference between
177    // a station global and a file global.
178    // `false` means do not prompt if another process changed the file first.
179    // An example must never raise a dialog, and neither must a headless host.
180    engine.commit_globals_to_disk(false)?;
181    println!("  committed to disk");
182
183    Ok(())
184}
More examples
Hide additional examples
examples/execution_run_subsequence.rs (lines 70-76)
59fn build(engine: &Engine) -> Result<SequenceFile, Error> {
60    let sequence_file = engine.new_sequence_file()?;
61
62    let main_sequence = sequence_file.get_sequence_by_name("MainSequence")?;
63    for name in ["Initialize", "Run Test", "Shut Down"] {
64        add_action(engine, &main_sequence, name)?;
65    }
66
67    let diagnostics = engine.new_sequence()?;
68    diagnostics.set_name(SUBSEQUENCE)?;
69    // A parameter is an ordinary property in the sequence's Parameters scope.
70    diagnostics.parameters()?.new_sub_property(
71        PARAMETER,
72        PropValType::String,
73        false,
74        "",
75        insert_if_missing(),
76    )?;
77    for name in ["Check Power Rails", "Check Clock"] {
78        add_action(engine, &diagnostics, name)?;
79    }
80    sequence_file.insert_sequence(&diagnostics)?;
81
82    Ok(sequence_file)
83}
examples/data_type_manage.rs (lines 128-134)
107fn main() -> Result<(), Box<dyn std::error::Error>> {
108    let engine = Engine::new()?;
109    let sequence_file = engine.new_sequence_file()?;
110    let file = sequence_file.as_property_object_file()?;
111    let types = file.type_usage_list()?;
112
113    types.insert_type(
114        &build_multimeter_type(&engine)?,
115        types.num_types()?,
116        TypeCategory::CustomDataTypes,
117    )?;
118    let coupling = register_enum(
119        &engine,
120        &types,
121        "Coupling",
122        &[("AC", 0.0), ("DC", 1.0)],
123        true,
124    )?;
125
126    // A variable of the enum type, so the change below has an instance to update.
127    let main_sequence = sequence_file.get_sequence_by_name("MainSequence")?;
128    main_sequence.locals()?.new_sub_property(
129        "InputCoupling",
130        PropValType::NamedType,
131        false,
132        "Coupling",
133        INSERT_IF_MISSING,
134    )?;
135
136    println!(
137        "Registered custom data types ({} in file):",
138        types.num_types()?
139    );
140    print_enumerators(&coupling)?;
141
142    // Evolve it: add an enumerator and raise the version.
143    println!(
144        "\nCoupling version before update: {}",
145        coupling.type_version()?
146    );
147    coupling.update_enumerators(&enumerator_array(
148        &engine,
149        &[("AC", 0.0), ("DC", 1.0), ("GND", 2.0)],
150        true,
151    )?)?;
152
153    // Raising the lowest field signals a change the engine applies silently;
154    // raising a higher one marks it as deliberate.
155    let version = coupling.type_version()?;
156    let mut fields = version.split('.');
157    let major: u32 = fields.next().unwrap_or("0").parse().unwrap_or(0);
158    let minor: u32 = fields.next().unwrap_or("0").parse().unwrap_or(0);
159    coupling.set_type_version(&format!("{major}.{}.0.0", minor + 1))?;
160    println!(
161        "Coupling version after  update: {}",
162        coupling.type_version()?
163    );
164
165    println!("\nCoupling now defines (InputCoupling reflects this):");
166    print_enumerators(&coupling)?;
167
168    // Saving does nothing unless the file believes it changed.
169    file.inc_change_count()?;
170    let path = std::env::temp_dir().join("rs_teststand_with_custom_types.seq");
171    sequence_file.save(&path.to_string_lossy())?;
172    println!("\nSaved to {}", path.display());
173
174    engine.release_sequence_file_ex(sequence_file, NO_OPTIONS)?;
175    Ok(())
176}
Source

pub fn delete_sub_property( &self, lookup_string: &str, options: i32, ) -> Result<(), Error>

Deletes a sub-property by lookup path (PropertyObject.DeleteSubProperty).

§Errors

Error if the COM call fails.

Examples found in repository?
examples/variables_manage.rs (line 43)
38fn temporary_variable_lifecycle(container: &PropertyObject) -> Result<(), rs_teststand::Error> {
39    let name = "TempScratch";
40    let clone_name = "TempScratchCopy";
41    for stale in [name, clone_name] {
42        if container.exists(stale, 0)? {
43            container.delete_sub_property(stale, 0)?;
44        }
45    }
46
47    container.new_sub_property(name, PropValType::String, false, "", INSERT_IF_MISSING)?;
48    container.set_val_string(name, 0, "scratch")?;
49    println!(
50        "  created {name} (String) = '{}'",
51        container.get_val_string(name, 0)?
52    );
53
54    container.delete_sub_property(name, 0)?;
55    container.new_sub_property(name, PropValType::Number, false, "", INSERT_IF_MISSING)?;
56    container.set_val_number(name, 0, 42.0)?;
57    println!(
58        "  retyped {name} -> Number = {}",
59        container.get_val_number(name, 0)?
60    );
61
62    // clone copies value and type; set_property_object attaches it under a new name.
63    let copy = container.clone_property(name, 0)?;
64    container.set_property_object(clone_name, INSERT_IF_MISSING, &copy)?;
65    println!(
66        "  cloned  {name} -> {clone_name} = {}",
67        container.get_val_number(clone_name, 0)?
68    );
69
70    container.delete_sub_property(clone_name, 0)?;
71    container.delete_sub_property(name, 0)?;
72    println!(
73        "  removed both: {name} exists={}, {clone_name} exists={}",
74        container.exists(name, 0)?,
75        container.exists(clone_name, 0)?
76    );
77    Ok(())
78}
Source

pub fn clone_property( &self, lookup_string: &str, options: i32, ) -> Result<Self, Error>

Clones a sub-property by lookup path (PropertyObject.Clone).

§Errors

Error if the COM call fails or returns an unexpected type.

Examples found in repository?
examples/template_manage_complex.rs (line 78)
72fn append_template(group: &PropertyObject, template: &PropertyObject) -> Result<(), Error> {
73    let index = group.get_num_elements()?;
74    group.set_num_elements(index + 1, PropertyOptions::NONE.bits())?;
75    group.set_property_object(
76        &format!("[{index}]"),
77        PropertyOptions::NONE.bits(),
78        &template.clone_property("", PropertyOptions::NONE.bits())?,
79    )
80}
81
82/// Finds a prototype in the group by name.
83fn find_template(group: &PropertyObject, name: &str) -> Result<Option<PropertyObject>, Error> {
84    for index in 0..group.get_num_elements()? {
85        let candidate = group.get_property_object_by_offset(index, PropertyOptions::NONE.bits())?;
86        if candidate.name()? == name {
87            return Ok(Some(candidate));
88        }
89    }
90    Ok(None)
91}
92
93/// Turns "not found" into an error naming the template that is missing.
94fn require(found: Option<PropertyObject>, name: &'static str) -> Result<PropertyObject, Error> {
95    found.ok_or(Error::UnexpectedType {
96        expected: name,
97        actual: "no template of that name in the group",
98    })
99}
100
101/// A configured Statement step, taken as a property tree so it can be cloned.
102fn step_template(engine: &Engine) -> Result<PropertyObject, Error> {
103    let step = engine.new_step(NO_ADAPTER, "Statement")?;
104    step.set_name(STEP_TEMPLATE)?;
105    step.set_post_expression(r#"Locals.Result = "Hello from step template!""#)?;
106    step.as_property_object()
107}
108
109/// A whole sequence, one step included, as a single prototype.
110fn sequence_template(engine: &Engine) -> Result<PropertyObject, Error> {
111    let sequence = engine.new_sequence()?;
112    sequence.set_name(SEQUENCE_TEMPLATE)?;
113
114    let step = engine.new_step(NO_ADAPTER, "Statement")?;
115    step.set_name("Inside_Sequence_Template")?;
116    sequence.insert_step(&step, 0, StepGroup::Main)?;
117
118    sequence.as_property_object()
119}
120
121/// A string variable carrying its default value.
122fn variable_template(engine: &Engine) -> Result<PropertyObject, Error> {
123    let variable =
124        engine.new_property_object(PropValType::String, false, "", PropertyOptions::NONE.bits())?;
125    variable.set_name(VARIABLE_TEMPLATE)?;
126    variable.set_val_string("", PropertyOptions::NONE.bits(), "Template Variable Value")?;
127    Ok(variable)
128}
129
130/// Stamps one copy of each template into a file that is already on disk.
131fn apply_templates(
132    sequence_file: &SequenceFile,
133    step: &PropertyObject,
134    sequence: &PropertyObject,
135    variable: &PropertyObject,
136) -> Result<(), Error> {
137    let main_sequence = sequence_file.get_sequence_by_name(MAIN_SEQUENCE)?;
138
139    // Inserting returns the copy on the Step interface, so it can be renamed
140    // and re-identified straight away. Without a new step ID the copy would go
141    // on claiming the prototype's identity.
142    let inserted_step = main_sequence.insert_step_from_template(step, 0, StepGroup::Main)?;
143    inserted_step.set_name("Step From Template")?;
144    inserted_step.create_new_unique_step_id()?;
145    println!("  step:     {}", inserted_step.name()?);
146
147    let inserted_sequence = sequence_file.insert_sequence_from_template(sequence)?;
148    inserted_sequence.create_new_unique_step_ids()?;
149    println!(
150        "  sequence: {} ({} step(s))",
151        inserted_sequence.name()?,
152        inserted_sequence.get_num_steps(StepGroup::Main)?
153    );
154
155    // A variable is the easy case: Locals is a property tree, so a clone drops
156    // straight in under the template's own name.
157    let locals = main_sequence.locals()?;
158    locals.set_property_object(
159        &variable.name()?,
160        PropertyOptions::INSERT_IF_MISSING.bits(),
161        &variable.clone_property("", PropertyOptions::NONE.bits())?,
162    )?;
163    println!(
164        "  variable: {} = {:?}",
165        variable.name()?,
166        locals.get_val_string(&variable.name()?, PropertyOptions::NONE.bits())?
167    );
168
169    // A file that does not believe it changed will not write anything.
170    sequence_file
171        .as_property_object_file()?
172        .inc_change_count()?;
173    Ok(())
174}
More examples
Hide additional examples
examples/variables_manage.rs (line 63)
38fn temporary_variable_lifecycle(container: &PropertyObject) -> Result<(), rs_teststand::Error> {
39    let name = "TempScratch";
40    let clone_name = "TempScratchCopy";
41    for stale in [name, clone_name] {
42        if container.exists(stale, 0)? {
43            container.delete_sub_property(stale, 0)?;
44        }
45    }
46
47    container.new_sub_property(name, PropValType::String, false, "", INSERT_IF_MISSING)?;
48    container.set_val_string(name, 0, "scratch")?;
49    println!(
50        "  created {name} (String) = '{}'",
51        container.get_val_string(name, 0)?
52    );
53
54    container.delete_sub_property(name, 0)?;
55    container.new_sub_property(name, PropValType::Number, false, "", INSERT_IF_MISSING)?;
56    container.set_val_number(name, 0, 42.0)?;
57    println!(
58        "  retyped {name} -> Number = {}",
59        container.get_val_number(name, 0)?
60    );
61
62    // clone copies value and type; set_property_object attaches it under a new name.
63    let copy = container.clone_property(name, 0)?;
64    container.set_property_object(clone_name, INSERT_IF_MISSING, &copy)?;
65    println!(
66        "  cloned  {name} -> {clone_name} = {}",
67        container.get_val_number(clone_name, 0)?
68    );
69
70    container.delete_sub_property(clone_name, 0)?;
71    container.delete_sub_property(name, 0)?;
72    println!(
73        "  removed both: {name} exists={}, {clone_name} exists={}",
74        container.exists(name, 0)?,
75        container.exists(clone_name, 0)?
76    );
77    Ok(())
78}

Trait Implementations§

Source§

impl Debug for PropertyObject

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. 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.