qfall_math/integer/z/
ownership.rs

1// Copyright © 2023 Niklas Siemer
2//
3// This file is part of qFALL-math.
4//
5// qFALL-math is free software: you can redistribute it and/or modify it under
6// the terms of the Mozilla Public License Version 2.0 as published by the
7// Mozilla Foundation. See <https://mozilla.org/en-US/MPL/2.0/>.
8
9//! This module contains implementations of functions
10//! important for ownership such as the [`Clone`] and [`Drop`] trait.
11//!
12//! The explicit functions contain the documentation.
13
14use super::Z;
15use flint_sys::fmpz::{fmpz, fmpz_clear, fmpz_init_set};
16
17impl Clone for Z {
18    /// Clones the given element and returns a deep clone of the [`Z`] element.
19    ///
20    /// # Examples
21    /// ```
22    /// use qfall_math::integer::Z;
23    ///
24    /// let a = Z::from(1);
25    /// let b = a.clone();
26    /// ```
27    fn clone(&self) -> Self {
28        // a fresh fmpz value is created, set to the same value as the cloned one,
29        // and wrapped in a new [`Z`] value. Hence, a fresh deep clone is created.
30        let mut value = fmpz(0);
31        unsafe { fmpz_init_set(&mut value, &self.value) };
32        Self { value }
33    }
34}
35
36impl Drop for Z {
37    /// Drops the given [`Z`] value and frees the allocated memory.
38    ///
39    /// # Examples
40    /// ```
41    /// use qfall_math::integer::Z;
42    /// {
43    ///     let a = Z::from(3);
44    /// } // as a's scope ends here, it get's dropped
45    /// ```
46    ///
47    /// ```
48    /// use qfall_math::integer::Z;
49    ///
50    /// let a = Z::from(3);
51    /// drop(a); // explicitly drops a's value
52    /// ```
53    fn drop(&mut self) {
54        // According to FLINT's documentation:
55        // "Clears the given fmpz_t, releasing any memory associated with it,
56        // either back to the stack or the OS, depending on whether the reentrant
57        // or non-reentrant version of FLINT is built."
58        // Hence, any memory allocated for values larger than 2^62 is freed. The left
59        // `i64` value is dropped automatically when the variable runs out of scope.
60
61        unsafe { fmpz_clear(&mut self.value) }
62    }
63}
64
65/// Test that the [`Clone`] trait is correctly implemented.
66#[cfg(test)]
67mod test_clone {
68    use super::Z;
69
70    /// check if large positive and negative values are cloned correctly
71    /// additionally check if values are stored at different places in memory
72    #[test]
73    fn large_int() {
74        let max_1 = Z::from(u64::MAX);
75        let min_1 = Z::from(i64::MIN);
76
77        let max_2 = max_1.clone();
78        let min_2 = min_1.clone();
79
80        assert_ne!(max_1.value.0, max_2.value.0);
81        assert_ne!(min_1.value.0, min_2.value.0);
82        assert_eq!(max_1, max_2);
83        assert_eq!(min_1, min_2);
84    }
85
86    /// check if small positive, negative and zero values are cloned correctly
87    /// additionally, check if the values are kept on the stack
88    #[test]
89    fn small_int() {
90        let pos_1 = Z::from(16);
91        let zero_1 = Z::ZERO;
92        let neg_1 = Z::from(-16);
93
94        let pos_2 = pos_1.clone();
95        let zero_2 = zero_1.clone();
96        let neg_2 = neg_1.clone();
97
98        assert_eq!(pos_1.value.0, pos_2.value.0);
99        assert_eq!(zero_1.value.0, zero_2.value.0);
100        assert_eq!(neg_1.value.0, neg_2.value.0);
101        assert_eq!(pos_1, pos_2);
102        assert_eq!(zero_1, zero_2);
103        assert_eq!(neg_1, neg_2);
104    }
105
106    /// check if a cloned value is still alive after the original value ran out of scope
107    #[test]
108    #[allow(clippy::redundant_clone)]
109    fn keep_alive() {
110        let a: Z;
111        {
112            let b = Z::from(5);
113            a = b.clone();
114        }
115        assert_eq!(a, Z::from(5));
116    }
117}
118
119/// Test that the [`Drop`] trait is correctly implemented.
120#[cfg(test)]
121mod test_drop {
122    use super::Z;
123
124    /// Check whether freed memory is reused afterwards
125    #[test]
126    fn free_memory() {
127        let a = Z::from(u64::MAX);
128        let b = Z { value: a.value };
129
130        drop(a);
131
132        // instantiate different [`Z`] value to check if memory slot is reused for different value
133        let c = Z::from(i64::MIN);
134        assert_eq!(c.value.0, b.value.0);
135
136        // memory slots differ due to previously created large integer
137        assert_ne!(b.value.0, Z::from(u64::MAX).value.0);
138    }
139
140    /// This test shows why false copies are a problem, which are prevented for users of the library
141    /// due to attribute privacy of the `value` attribute in [`Z`]
142    #[test]
143    fn memory_equality() {
144        let a = Z::from(u64::MAX);
145        // false clone/ copy of [`Z`] value allows for the [`fmpz`] value to be kept alive
146        // after one reference was dropped and its referenced memory was freed
147        let b = Z { value: a.value };
148
149        drop(a);
150
151        // any large new integer created is filled in the same memory space
152        // as fmpz_equal first checks whether the pointers point to the same point in memory
153        // and are then assumed to be the same, as they both point to the same value, these
154        // values are equal afterwards. Even though, `b` pointed to a different value previously.
155        assert_eq!(b, Z::from(i64::MIN));
156    }
157}