Skip to main content

qfall_math/integer/z/
ownership.rs

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