guard_clause/
lib.rs

1#![cfg_attr(not(test), no_std)]
2#![cfg_attr(not(test), no_main)]
3// By Group: Cargo, Pedantic
4// Probably by Clippy defaults: Complexity, Correctness, Performance, Suspicious
5// Added manually: Restriction, Style
6// Not included: Nursery
7// Lints up to 1.84.0
8#![deny(clippy::pedantic)]
9#![deny(clippy::cargo)]
10#![deny(clippy::absolute_paths)]
11#![deny(clippy::alloc_instead_of_core)]
12#![deny(clippy::allow_attributes)]
13#![deny(clippy::allow_attributes_without_reason)]
14#![allow(
15    clippy::arbitrary_source_item_ordering,
16    reason = "Kelvin Embedded style guide: prefer ordering for logical reasons over alphabetical."
17)]
18#![allow(
19    clippy::arithmetic_side_effects,
20    reason = "Kelvin Embedded style guide: Arithmetic side effects commonly used in embedded systems programming."
21)]
22#![allow(
23    clippy::as_conversions,
24    reason = "clippy::as_conversions explanation lost in history. TODO remove allow and find reason."
25)]
26// #![deny(clippy::as_pointer_underscore)] // Unlock when lint released. Originally reported as 1.81.0
27#![deny(clippy::as_underscore)]
28#![deny(clippy::assertions_on_constants)]
29#![deny(clippy::big_endian_bytes)]
30#![deny(clippy::byte_char_slices)]
31#![deny(clippy::cfg_not_test)]
32#![deny(clippy::clone_on_ref_ptr)]
33#![deny(clippy::create_dir)]
34#![deny(clippy::dbg_macro)]
35#![deny(clippy::decimal_literal_representation)]
36#![deny(clippy::default_numeric_fallback)]
37#![deny(clippy::default_union_representation)]
38#![deny(clippy::deref_by_slicing)]
39#![deny(clippy::disallowed_script_idents)]
40// #![deny(clippy::doc_include_without_cfg)]  // Unlock when 1.84.0 released.
41#![allow(
42    clippy::duplicated_attributes,
43    reason = "https://github.com/rust-lang/rust-clippy/issues/13500"
44)]
45#![deny(clippy::else_if_without_else)]
46#![deny(clippy::empty_drop)]
47#![deny(clippy::empty_enum_variants_with_brackets)]
48#![deny(clippy::empty_structs_with_brackets)]
49#![deny(clippy::error_impl_error)]
50#![allow(
51    clippy::exhaustive_enums,
52    reason = "Kelvin Embedded style guide: allowing public enumerants is consistent with a functional style use of enumerations as pure data."
53)]
54#![allow(
55    clippy::exhaustive_structs,
56    reason = "Kelvin Embedded style guide: allowing public field access is consistent with a functional style use of data structures as pure data."
57)]
58#![deny(clippy::exit)]
59#![deny(clippy::expect_used)]
60#![deny(clippy::field_scoped_visibility_modifiers)]
61#![deny(clippy::filetype_is_file)]
62#![deny(clippy::filter_map_bool_then)]
63#![deny(clippy::float_arithmetic)]
64#![deny(clippy::float_cmp_const)]
65#![deny(clippy::fn_to_numeric_cast_any)]
66#![deny(clippy::format_push_string)]
67#![deny(clippy::get_unwrap)]
68#![deny(clippy::host_endian_bytes)]
69#![deny(clippy::if_then_some_else_none)]
70#![deny(clippy::impl_trait_in_params)]
71#![allow(
72    clippy::implicit_return,
73    reason = "Kelvin Embedded Style Guide: Implicit returns are an idiomatic approach that improves code readability."
74)]
75#![deny(clippy::indexing_slicing)]
76#![deny(clippy::infinite_loop)]
77#![deny(clippy::inline_asm_x86_att_syntax)]
78#![allow(
79    clippy::inline_asm_x86_intel_syntax,
80    reason = "clippy::inline_asm_x86_intel_syntax explanation lost in history. TODO remove allow and find reason."
81)]
82#![allow(
83    clippy::integer_division,
84    reason = "Kelvin Embedded Style Guide: Integer division is a normally used operation in embedded systems programming."
85)]
86#![allow(
87    clippy::integer_division_remainder_used,
88    reason = "Kelvin Embedded Style Guide: Integer division is a normally used operation in embedded systems programming."
89)]
90#![deny(clippy::items_after_test_module)]
91#![deny(clippy::iter_over_hash_type)]
92#![deny(clippy::large_include_file)]
93#![deny(clippy::legacy_numeric_constants)]
94#![deny(clippy::let_underscore_must_use)]
95#![deny(clippy::let_underscore_untyped)]
96#![allow(
97    clippy::little_endian_bytes,
98    reason = "Little Endian is both our target and host endianness. Preference is specifying explixit over local."
99)]
100#![deny(clippy::lossy_float_literal)]
101#![deny(clippy::manual_is_finite)]
102#![deny(clippy::manual_is_infinite)]
103#![deny(clippy::manual_is_power_of_two)]
104#![deny(clippy::manual_next_back)]
105#![deny(clippy::manual_pattern_char_comparison)]
106#![deny(clippy::manual_rotate)]
107#![deny(clippy::manual_while_let_some)]
108#![deny(clippy::map_all_any_identity)]
109#![deny(clippy::map_err_ignore)]
110#![deny(clippy::map_with_unused_argument_over_ranges)]
111#![deny(clippy::mem_forget)]
112#![allow(
113    clippy::min_ident_chars,
114    reason = "Single characters are useful in small namespaces and should not be mechanically prohibited."
115)]
116#![deny(clippy::missing_assert_message)]
117#![allow(
118    clippy::missing_asserts_for_indexing,
119    reason = "Asserts panic when false, which is prohibited in the embedded system."
120)]
121#![allow(
122    clippy::missing_docs_in_private_items,
123    reason = "clippy::missing_docs_in_private_items explanation lost in history. TODO remove allow and find reason."
124)]
125#![deny(clippy::missing_enforced_import_renames)]
126#![allow(
127    clippy::missing_inline_in_public_items,
128    reason = "clippy::missing_inline_in_public_items explanation lost in history. TODO remove allow and find reason."
129)]
130#![deny(clippy::missing_trait_methods)]
131#![deny(clippy::mixed_attributes_style)]
132#![deny(clippy::mixed_read_write_in_expression)]
133#![deny(clippy::mod_module_files)]
134#![deny(clippy::module_name_repetitions)]
135#![allow(
136    clippy::modulo_arithmetic,
137    reason = "clippy::modulo_arithmetic explanation lost in history. TODO remove allow and find reason."
138)]
139#![allow(
140    clippy::multiple_crate_versions,
141    reason = "clippy::multiple_crate_versions explanation lost in history. TODO remove allow and find reason."
142)]
143#![deny(clippy::multiple_inherent_impl)]
144#![deny(clippy::multiple_unsafe_ops_per_block)]
145#![deny(clippy::mutex_atomic)]
146#![deny(clippy::needless_as_bytes)]
147#![deny(clippy::needless_borrows_for_generic_args)]
148#![deny(clippy::needless_else)]
149#![deny(clippy::needless_pub_self)]
150#![deny(clippy::needless_raw_strings)]
151#![deny(clippy::needless_return_with_question_mark)]
152#![deny(clippy::non_ascii_literal)]
153#![deny(clippy::non_minimal_cfg)]
154#![deny(clippy::non_zero_suggestions)]
155#![deny(clippy::option_map_or_err_ok)]
156#![deny(clippy::panic)]
157#![deny(clippy::panic_in_result_fn)]
158#![deny(clippy::partial_pub_fields)]
159#![deny(clippy::pathbuf_init_then_push)]
160#![deny(clippy::pattern_type_mismatch)]
161#![deny(clippy::print_stderr)]
162#![deny(clippy::print_stdout)]
163#![deny(clippy::pub_use)]
164#![allow(
165    clippy::pub_with_shorthand,
166    reason = "Denying the reciprocal pub_without_shorthand."
167)]
168#![deny(clippy::pub_without_shorthand)]
169#![allow(
170    clippy::question_mark_used,
171    reason = "Allowed by Kelvin Style Guide. This is idiomatic Rust."
172)]
173#![deny(clippy::rc_buffer)]
174#![deny(clippy::rc_mutex)]
175#![deny(clippy::redundant_feature_names)]
176#![deny(clippy::redundant_type_annotations)]
177#![deny(clippy::ref_patterns)]
178#![deny(clippy::renamed_function_params)]
179#![deny(clippy::rest_pat_in_fully_bound_structs)]
180#![allow(
181    clippy::same_name_method,
182    reason = "TODO reconsider. Due to bitflags! macro"
183)]
184#![deny(clippy::self_named_module_files)]
185#![deny(clippy::semicolon_outside_block)]
186#![allow(
187    clippy::separated_literal_suffix,
188    reason = "clippy::separated_literal_suffix explanation lost in history. TODO remove allow and find reason."
189)]
190#![deny(clippy::shadow_reuse)]
191#![deny(clippy::shadow_same)]
192#![deny(clippy::shadow_unrelated)]
193#![allow(
194    clippy::single_call_fn,
195    reason = "Allowed by Kelvin style guide. This is best practice when creating well refactored code."
196)]
197#![allow(
198    clippy::single_char_lifetime_names,
199    reason = "Allowed by Kelvin style guide. Single character lifetime names are commonly used in idiomatic Rust."
200)]
201#![deny(clippy::std_instead_of_alloc)]
202#![deny(clippy::std_instead_of_core)]
203#![deny(clippy::str_to_string)]
204#![deny(clippy::string_add)]
205#![deny(clippy::string_lit_chars_any)]
206#![deny(clippy::string_slice)]
207#![deny(clippy::string_to_string)]
208#![deny(clippy::suspicious_xor_used_as_pow)]
209#![deny(clippy::tests_outside_test_module)]
210#![deny(clippy::to_string_trait_impl)]
211#![deny(clippy::todo)]
212#![deny(clippy::try_err)]
213#![deny(clippy::undocumented_unsafe_blocks)]
214#![deny(clippy::unimplemented)]
215#![deny(clippy::unnecessary_fallible_conversions)]
216#![deny(clippy::unnecessary_map_or)]
217#![deny(clippy::unnecessary_safety_comment)]
218#![deny(clippy::unnecessary_safety_doc)]
219#![deny(clippy::unnecessary_self_imports)]
220#![deny(clippy::unneeded_field_pattern)]
221#![deny(clippy::unreachable)]
222#![deny(clippy::unseparated_literal_suffix)]
223#![deny(clippy::unused_enumerate_index)]
224#![deny(clippy::unused_result_ok)]
225#![deny(clippy::unused_trait_names)]
226#![deny(clippy::unwrap_in_result)]
227#![deny(clippy::unwrap_used)]
228#![deny(clippy::use_debug)]
229#![deny(clippy::verbose_file_reads)]
230#![deny(clippy::wildcard_enum_match_arm)]
231
232/// Checks that a boolean expression is `true` at runtime, otherwise performs
233/// a return with the second parameter, short-circuiting the operation of the
234/// enclosing function.
235///
236/// # Uses
237///
238/// Guard clauses are used, generally at the start of functions, to ensure that
239/// conditions are valid for the operation of the function. The usage is similar
240/// to an assertion, but the guard clause returns a value that may be evaluated
241/// and acted on by the calling function, while assertions end the program by
242/// panicking.
243///
244/// The guard macro is syntactic sugar that replaces an if clause that would
245/// return when true. The guard macro can be written on one line, while the
246/// default Rust code formatter expands any if clause to three lines.
247///
248/// # Examples
249///
250/// ## Guard returning on Option
251///
252/// ```rust
253/// use guard_clause::guard;
254///
255/// fn safe_divide_using_option(lval: i32, rval: i32) -> Option<i32> {
256///     guard!(rval != 0, None);
257///     Some(lval / rval)
258/// }
259/// assert_eq!(Some(3), safe_divide_using_option(6, 2));
260/// assert_eq!(None, safe_divide_using_option(6, 0));
261/// ```
262///
263/// ## Guard returning a string
264///
265/// ``` rust
266/// use guard_clause::guard;
267///
268/// fn safe_divide_using_result_str(lval: i32, rval: i32) -> Result<i32, &'static str> {
269///     guard!(rval != 0, Err("Divide by zero!"));
270///     Ok(lval / rval)
271/// }
272/// assert_eq!(Ok(3), safe_divide_using_result_str(6, 2));
273/// assert_eq!(Err("Divide by zero!"), safe_divide_using_result_str(6, 0));
274/// ```
275///
276/// ## Guard returning a enum
277///
278/// ``` rust
279/// use guard_clause::guard;
280///
281/// #[derive(Debug, PartialEq)]
282/// enum DivError {
283///     DivideByZero
284/// }
285/// fn safe_divide_using_result_enum(lval: i32, rval: i32) -> Result<i32, DivError> {
286///     guard!(rval != 0, Err(DivError::DivideByZero));
287///     Ok(lval / rval)
288/// }
289/// assert_eq!(Ok(3), safe_divide_using_result_enum(6, 2));
290/// assert_eq!(Err(DivError::DivideByZero), safe_divide_using_result_enum(6, 0));
291/// ```
292///
293/// ## Guard returning the unit type: '()'
294///
295/// ``` rust
296/// use guard_clause::guard;
297/// fn increments_if_zero(i: &mut i32) {
298///     guard!(*i == 0);
299///     *i += 1;
300/// }
301///
302/// let mut i = 0;
303/// increments_if_zero(&mut i);
304/// assert_eq!(1, i);
305///
306/// let mut j = 2;
307/// increments_if_zero(&mut j);
308/// assert_eq!(2, j);
309/// ```
310#[macro_export]
311macro_rules! guard {
312    ($check:expr, $fail_return:expr) => {
313        if !$check {
314            return $fail_return;
315        }
316    };
317    ($check:expr) => {
318        if !$check {
319            return;
320        }
321    };
322}
323
324#[cfg(test)]
325mod tests {
326    fn use_guard_with_result(test: bool, guard_fail: TestError) -> Result<(), TestError> {
327        guard!(test, Err(guard_fail));
328        Ok(())
329    }
330    #[test]
331    fn given_a_true_condition_guard_with_result_return_type_then_ok_with_enum() {
332        assert_eq!(use_guard_with_result(true, TestError::Reason2), Ok(()));
333    }
334    #[test]
335    fn given_a_false_condition_guard_with_result_return_type_then_err_with_enum() {
336        let expected_err = TestError::Reason2;
337        assert_eq!(
338            use_guard_with_result(false, expected_err),
339            Err(expected_err)
340        );
341    }
342
343    fn use_guard_with_option(test: bool) -> Option<()> {
344        guard!(test, None);
345        Some(())
346    }
347    #[test]
348    fn given_a_true_condition_guard_with_option_return_type_then_ok_with_enum() {
349        assert_eq!(use_guard_with_option(true), Some(()));
350    }
351    #[test]
352    fn given_a_false_condition_guard_with_option_return_type_then_err_with_enum() {
353        assert_eq!(use_guard_with_option(false), None);
354    }
355
356    fn use_guard_with_string_slice(test: bool, guard_fail: &'static str) -> &'static str {
357        guard!(test, guard_fail);
358        GOOD_STRING_SLICE
359    }
360    #[test]
361    fn given_a_true_condition_guard_with_string_slice_return_type_then_ok_with_enum() {
362        assert_eq!(
363            use_guard_with_string_slice(true, BAD_STRING_SLICE),
364            GOOD_STRING_SLICE
365        );
366    }
367    #[test]
368    fn given_a_false_condition_guard_with_string_slice_return_type_then_err_with_enum() {
369        assert_eq!(
370            use_guard_with_string_slice(false, BAD_STRING_SLICE),
371            BAD_STRING_SLICE
372        );
373    }
374
375    fn use_guard_with_no_return(test: bool, run_counter: &mut usize) {
376        guard!(test);
377        *run_counter += 1;
378    }
379    #[test]
380    fn given_a_true_condition_guard_with_no_return_type_then_no_return_and_code_after_guard_ran() {
381        let mut run_counter = 0;
382        use_guard_with_no_return(true, &mut run_counter);
383        assert_eq!(1, run_counter);
384    }
385    #[test]
386    fn given_a_false_condition_guard_with_no_return_type_then_no_return_but_code_after_guard_not_run(
387    ) {
388        let mut run_counter = 0;
389        use_guard_with_no_return(false, &mut run_counter);
390        assert_eq!(0, run_counter);
391    }
392
393    #[derive(Debug, PartialEq, Clone, Copy)]
394    enum TestError {
395        _Reason1,
396        Reason2,
397        _Reason3(&'static str),
398    }
399
400    const GOOD_STRING_SLICE: &str = "Good!";
401    const BAD_STRING_SLICE: &str = "bad...";
402}