How to avoid memory leaks when building a slice of slices using ArrayLists - memory-leaks

I'm trying to build a slice of slices using multiple std.ArrayLists.
The code below works, but the memory allocator std.testing.allocator warns me of a memory leaks wherever I append new elements to a sublist.
const std = #import("std");
const mem = std.mem;
fn sliceOfSlices(allocator: *mem.Allocator) ![][]usize {
var list = std.ArrayList([]usize).init(allocator);
var i: usize = 0;
while (i < 3) : (i += 1) {
var sublist = std.ArrayList(usize).init(allocator);
// errdefer sublist.deinit(); // here?
var n: usize = 0;
while (n < 5) : (n += 1) {
try sublist.append(n); // leaks
// errdefer sublist.deinit(); // here?
// errdefer allocator.free(sublist.items);
}
try list.append(sublist.toOwnedSlice());
}
return list.toOwnedSlice();
}
const testing = std.testing;
test "memory leaks" {
const slice = try sliceOfSlices(testing.allocator);
testing.expectEqual(#intCast(usize, 3), slice.len);
testing.expectEqual(#intCast(usize, 5), slice[0].len);
}
I tried to use errdefer in several places to free the allocated sublist, but it didn't work. From the documentation it seems a lifetime issue, but I'm not sure how to handle it.
the std.ArrayList(T).items slice has a lifetime that remains valid until the next time the list is resized, such as by appending new elements.
— https://ziglang.org/documentation/master/#Lifetime-and-Ownership
What's the appropriate error handling when list.append() fails?

I am a beginner to zig so perhaps I am totally wrong here, but I think the reason why you are getting the memory leak is not because something failed!
As you use ArrayList its memory is allocated via an allocator, the memory has explicitly to be freed at end of usage. For an ArrayList you could simply use the deinit() function. However as your function sliceOfSlices() converts that ArrayList wrapper to a slice, you have to use testing.allocator.free(slice) to get rid of the memory used by that slice.
But note: every element of your slice is itself a slice (or a pointer to it). Also obtained via ArrayList.toOwnedSlice(). Therefore those slices you have also to get rid of, before you can deallocate the containing slice.
So I would change your test to
test "memory leaks" {
const slice = try sliceOfSlices(testing.allocator);
defer {
for (slice) |v| {
testing.allocator.free(v);
}
testing.allocator.free(slice);
}
testing.expectEqual(#intCast(usize, 3), slice.len);
testing.expectEqual(#intCast(usize, 5), slice[0].len);
}
and now no memory leak should occur anymore.
Perhaps somebody knows a better solution, but lacking experience here, this would be the way to go, IMO.
And after some thinking, answering your question what to do in case of an error, I would rewrite your function sliceOfSlices() to
fn sliceOfSlices(allocator: *mem.Allocator) ![][]usize {
var list = std.ArrayList([]usize).init(allocator);
errdefer {
for (list.items) |slice| {
allocator.free(slice);
}
list.deinit();
}
var i: usize = 0;
while (i < 3) : (i += 1) {
var sublist = std.ArrayList(usize).init(allocator);
errdefer sublist.deinit();
var n: usize = 0;
while (n < 5) : (n += 1) {
try sublist.append(n);
}
try list.append(sublist.toOwnedSlice());
}
return list.toOwnedSlice();
}
Now if any error happened in your function, both list and sublist should be cleaned up properly. Still if no error was returned from the function, your calling code would be responsible for the cleanup to avoid memory leaks like implemented in the test block above.

Related

include_str for null terminated string

I need to read a file into a null terminated string at compile time.
Working in Rust OpenGL. I have a shader source code stored in a separate file. The function that will eventually read the source is gl::ShaderSource from the gl crate. All it needs is a pointer to a null terminated string (the std::ffi::CStr type).
Typically the guides I have seen read the shader source file using include_str!, then at run time allocate a whole new buffer of length +1, then copy the original source into the new buffer and put the terminating 0 at the end. I'd like to avoid all that redundant allocating and copying and just have the correctly null terminated string at compile time.
I realize it is somewhat petty to want to avoid an extra allocation for a short shader file, but the principle could apply to many other types of larger constants.
While scrolling through suggested questions during the preview I saw this: How do I expose a compile time generated static C string through FFI?
which led me to this solution:
let bytes1 = concat!(include_str!("triangle.vertex_shader"), "\0");
let bytes2 = bytes1.as_bytes();
let bytes3 = unsafe {
CStr::from_bytes_with_nul_unchecked(bytes2)
};
println!("{:?}", bytes3);
Does this accomplish avoiding the runtime allocation and copying?
Your code is unsound. It fails to verify there are no interior NUL bytes.
You can use the following function to validate the string (at compile time, with no runtime cost):
pub const fn to_cstr(s: &str) -> &CStr {
let bytes = s.as_bytes();
let mut i = 0;
while i < (bytes.len() - 1) {
assert!(bytes[i] != b'\0', "interior byte cannot be NUL");
i += 1;
}
assert!(bytes[bytes.len() - 1] == b'\0', "last byte must be NUL");
// SAFETY: We verified there are no interior NULs and that the string ends with NUL.
unsafe { CStr::from_bytes_with_nul_unchecked(bytes) }
}
Wrap it in a little nice macro:
macro_rules! include_cstr {
( $path:literal $(,)? ) => {{
// Use a constant to force the verification to run at compile time.
const VALUE: &'static ::core::ffi::CStr = $crate::to_cstr(concat!(include_str!($path), "\0"));
VALUE
}};
}
Then use it:
let bytes = include_cstr!("triangle.vertex_shader");
If there are interior NUL bytes the code will fail to compile.
When CStr::from_bytes_with_nul() becomes const-stable, you will be able to replace to_cstr() with it.
Yes that should work as intended. If you want you can even bundle it into a simple macro.
macro_rules! include_cstr {
($file:expr) => {{
// Create as explicit constant to force from_bytes_with_nul_unchecked to
// perform compile time saftey checks.
const CSTR: &'static ::std::ffi::CStr = unsafe {
let input = concat!($file, "\0");
::std::ffi::CStr::from_bytes_with_nul_unchecked(input.as_bytes())
};
CSTR
}};
}
const VERTEX_SHADER: &'static CStr = include_cstr!("shaders/vert.glsl");
const FRAGMENT_SHADER: &'static CStr = include_cstr!("shaders/frag.glsl");

Rust SeqCst ordering

I'm trying to understand how Ordering::SeqCst works. For that I have few code examples where this ordering is mandatory for obtaining consistent result. In first example we just want to increment counter variable:
let a: &'static _ = Box::leak(Box::new(AtomicBool::new(false)));
let b: &'static _ = Box::leak(Box::new(AtomicBool::new(false)));
let counter: &'static _ = Box::leak(Box::new(AtomicUsize::new(0)));
let _thread_a = spawn(move || a.store(true, Ordering::Release));
let _thread_b = spawn(move || b.store(true, Ordering::Release));
let thread_1 = spawn(move || {
while !a.load(Ordering::Acquire) {} // prevents from reordering everything after
if b.load(ordering::Relaxed) { // no need of Acquire due to previous restriction
counter.fetch_add(1, Ordering::Relaxed);
}
});
let thread_2 = spawn(move || {
while !b.load(Ordering::Acquire) {} // prevents from reordering everything after
if a.load(ordering::Relaxed) { // no need of Acquire due to previous restriction
counter.fetch_add(1, Ordering::Relaxed);
}
});
thread_1.join().unwrap();
thread_2.join().unwrap();
println!("{}", counter.load(Ordering::Relaxed));
Possible values of counter with this example are 1 or 2, depends on thread scheduling. But surprisingly 0 is also possible but I don't understand how.
If thread_1 has started and only a was set to true by _thread_a, counter could will be left untouched after thread_1 will exit.
If thread_2 will start after thread_1, counter will be incremented once, bcs thread_1 has finished (here we know that a is already true), so thread_2 have just to wait for b to become true.
Or if thread_2 will be first and b was set to true, counter will be incremented only once too.
There is also possibility that _thread_a and _thread_b will both run before thread_1 and thread_2 and both of them will increment counter. So that's why 1 and 2 are valid possible outcomes for counter. But as I previously said, there is also a 0 as possible valid result, only if I won't replace all loads and stores for a and b to Ordering::SeqCst:
let _thread_a = spawn(move || a.store(true, Ordering::SeqCst));
let _thread_b = spawn(move || b.store(true, Ordering::SeqCst));
let thread_1 = spawn(move || {
while !a.load(Ordering::SeqCst) {}
if b.load(ordering::SeqCst) {
counter.fetch_add(1, Ordering::Relaxed);
}
});
let thread_2 = spawn(move || {
while !b.load(Ordering::SeqCst) {}
if a.load(ordering::SeqCst) {
counter.fetch_add(1, Ordering::Relaxed);
}
});
thread_1.join().unwrap();
thread_2.join().unwrap();
println!("{}", counter.load(Ordering::SeqCst));
Now 0 isn't possible, but I don't know why.
Second example was taken from here:
static A: AtomicBool = AtomicBool::new(false);
static B: AtomicBool = AtomicBool::new(false);
static mut S: String = String::new();
fn main() {
let a = thread::spawn(|| {
A.store(true, SeqCst);
if !B.load(SeqCst) {
unsafe { S.push('!') };
}
});
let b = thread::spawn(|| {
B.store(true, SeqCst);
if !A.load(SeqCst) {
unsafe { S.push('!') };
}
});
a.join().unwrap();
b.join().unwrap();
}
Threads a and b could start at same time and modify A and B thus none of them will modify S. Or one of them could start before the other, and modify S, leaving other thread with unmodified S. If I understood correctly, there is no possibility for S to being modified in parallel by both threads? The only reason why Ordering::SeqCst is useful here, to prevent from reordering. But if I will replace all ordering like this:
let a = thread::spawn(|| {
A.store(true, Release); // nothing can be placed before
if !B.load(Acquire) { // nothing can be reordered after
unsafe { S.push('!') };
}
});
let b = thread::spawn(|| {
B.store(true, Release); // nothing can be placed before
if !A.load(Acquire) { // nothing can be reordered after
unsafe { S.push('!') };
}
});
Isn't it the same as original?
Also Rust docs refers to C++ docs on ordering, where Ordering::SeqCst is described as:
Atomic operations tagged memory_order_seq_cst not only order memory the same way as release/acquire ordering (everything that happened-before a store in one thread becomes a visible side effect in the thread that did a load), but also establish a single total modification order of all atomic operations that are so tagged.
What is single total modification order on concrete example?
Although Chayim answer is correct, but I think it's still very difficult to wrap one's head around the concept. A useful mind model to remember is that reordering of operations might happen if it is not forbidden, and access different parts of memory from the perspective of the current thread.
In rustonomicon two types or reordering are described:
Compiler Reordering - if performance is improved while result is same why not reordering.
Hardware Reordering - this topic is more subtle and apparently rustonomicon reasoning about cache causing reordering is stricly speaking incorrect (thanks a lot to #peter-cordes for pointing that out). CPU caches are always coherent (MESI protocol guarantees invalidation of other copies before committing a write to a cache line). Out-of-order execution is one reason the reordering may happen in the CPU core but it is not the only reason. Here are two more examples of what may cause reordering in the CPU. And here is a detailed explanation of one of the examples (with still a lot of discussion afterwards).
And here is an excellent documentation (Howells, McKenney, Deacon, Zijlstra) touching both hardware and compiler which claims to still be incomplete.
We may use both hardware or compiler reordering for the first example:
let a: &'static _ = Box::leak(Box::new(AtomicBool::new(false)));
let b: &'static _ = Box::leak(Box::new(AtomicBool::new(false)));
let counter: &'static _ = Box::leak(Box::new(AtomicUsize::new(0)));
1) let _thread_a = spawn(move || a.store(true, Ordering::Release));
2) let _thread_b = spawn(move || b.store(true, Ordering::Release));
let thread_1 = spawn(move || {
3) while !a.load(Ordering::Acquire) {} // prevents from reordering everything after
4) if b.load(ordering::Relaxed) { // no need of Acquire due to previous restriction
counter.fetch_add(1, Ordering::Relaxed);
}
});
let thread_2 = spawn(move || {
5) while !b.load(Ordering::Acquire) {} // prevents from reordering everything after
6) if a.load(ordering::Relaxed) { // no need of Acquire due to previous restriction
counter.fetch_add(1, Ordering::Relaxed);
}
});
When thread2 is running on its core it has a and b and operations 5) and 6) in store. 6) is relaxed, 6) and 5) use different memory and don't affect each other, so there is nothing that prevents 6) happening before 5).
As for the second example:
let a = thread::spawn(|| {
1) A.store(true, Release); // nothing can be placed before
2) if !B.load(Acquire) { // nothing can be reordered after
unsafe { S.push('!') };
}
});
let b = thread::spawn(|| {
3) B.store(true, Release); // nothing can be placed before
4) if !A.load(Acquire) { // nothing can be reordered after
unsafe { S.push('!') };
}
});
there is this similar question talking about it.
If we look at this paper (it is for c++ but you rightly noticed that Rust uses same model), it describes the memory guarantees for Release and Acquire as:
atomic operations on the same object may never be reordered [CSC12,
1.10.19, p. 14],
(non-)atomic write operations that are sequenced before a release operation A may not be reordered after A,
(non-)atomic load operations that are sequenced after an acquire
operation A may not be reordered before A.
So in principle it is not forbiden to reorder 1) <-> 2) or 3) <-> 4).
You seem to misunderstand the basics of reordering.
We do not care what threads started first: it is possible for one thread to observe a result that means thread A was started first, while another thread will observe a result meaning thread B was started first.
In your first snippet, for example, it is possible for thread_1 to see a == true && b == false, yet thread_2 can at the same time see a == false && b == true. As long as there is no happens-before relationship that forces otherwise.
The only thing we care about (putting aside the global modification order) is the happens-before relationship. If point B established a happens-before relationship with point A, then everything before point A (including) will be observable after point B (including). But in snippet 1 we only establish a happens-before relationship with _thread_a for thread_1 and _thread_b for thread_2, leaving our relationship with the other thread that sets the other variable undefined, and therefore, the threads can observe opposite values of the variables. In snippet 2, we may not establish a happens-before relationship at all: we may establish a happens-before relationship between the other thread's store and our load, but our store is not included inside and therefore the other thread may not see it.
With SeqCst, however, all operations take place in the global modification order. This is a virtual list of all SeqCst operations, and each element in the list happens-after all elements before it and happens-before all elements after it. This can be used to establish a happens-before relationship between different variables atomic operations, like in your examples.

Rust lifetimes in if statement

I have an if statement in a for loop, and I want it to create a variable with the lifetime of that iteration of the for loop.
for condition_raw in conditions_arr {
println!("{}", condition_raw);
let matching = !condition_raw.contains('!');
if matching {
let index = condition_raw.find('=').unwrap_or_default();
} else {
let index = condition_raw.find('!').unwrap_or_default();
}
let key = &condition_raw[..index];
}
let key = &condition_raw[..index]; currently throws cannot find value index in this scope
not found in this scope rustc E0425
I'll ignore the condition variable which does not seem to be used at all in your example.
A let statement creates a binding that holds at most for the current scope. For this reason, when you create the index variable inside the if, you are not making it accessible anywhere else. There are two ways to solve this issue.
The first way is to explicitly declare index as being part of the outer scope, and only define it inside the if statement.
for condition_raw in conditions_arr {
let matching = !condition_raw.contains('!');
let index;
if matching {
index = condition_raw.find('=').unwrap_or_default();
} else {
index = condition_raw.find('!').unwrap_or_default();
}
let key = &condition_arr[..index];
}
There is no risk of accidentally not defining index, since Rust will make sure that index is defined (exactly once) in all possible branching of your code before it is used. Yet, it's not a pretty solution because it violates a "locality" principle, that is that pieces of code should have effects on or pieces of code that are sufficiently close. In this case, the let index; is not too far from its definition, but it could be arbitrarily far, which makes it painful for someone who reads your code to remember that there is a declared but not yet defined.
Alternatively, you could use the fact that most things in Rust are expressions:
for condition_raw in conditions_arr {
let matching = !condition_raw.contains('!');
let index = if matching {
condition_raw.find('=').unwrap_or_default();
} else {
condition_raw.find('!').unwrap_or_default();
}
let key = &condition_arr[..index];
}
But, in fact, you could factorize your code even more, which is usually better:
for condition_raw in conditions_arr {
let matching = !condition_raw.contains('!');
let index = condition_raw.find(if matching {
'='
} else {
'!'
}).unwrap_or_default();
let key = &condition_arr[..index];
Or, even more
for condition_raw in conditions_arr {
let index = condition_raw
.find('!')
.or_else(|| condition_raw.find('='))
.unwrap_or_default();
let key = &condition_arr[..index];
}
An idiomatic way to assign variables from an if else statement is as follows:
let index: usize = if matching {
condition_raw.find('=').unwrap_or_default()
} else {
condition_raw.find('!').unwrap_or_default()
};
Idiomatic way of assigning a value from an if else condition in Rust
In Rust, an if/else block is an expression. That is to say, the block itself has a value, equivalent to the last expression in whatever section was executed. With that in mind, I would structure your code like this:

Rust nested fors itering over the same vector

I was trying to change some of vector elements, while itering over the vector.
for operator in operators {
// add left side
let node = nodes[operator.index-1].clone();
nodes[operator.index].children.push(node);
// add right side
let node = nodes[operator.index+1].clone();
nodes[operator.index].children.push(node);
// remove used nodes
nodes.remove(operator.index+1);
nodes.remove(operator.index-1);
// two items were removed, so every index higher than the current one needs to be lowered by 2
for operator2 in &mut operators {
if operator2.index > operator.index {
operator2.index -= 2;
}
}
}
Sadly this isn't possible in rust, because error says that 'operators' was moved. I tried to change for to look like this:
for operator in &operators, but then it has problem getting 'operators' as mutable and immutable at the same time. What can I do to make it work?
Use simple loops with indices:
for operator_idx in 0..operators.len() {
let operator = &operators[operator_idx];
// add left side
let node = nodes[operator.index-1].clone();
nodes[operator.index].children.push(node);
// add right side
let node = nodes[operator.index+1].clone();
nodes[operator.index].children.push(node);
// remove used nodes
nodes.remove(operator.index+1);
nodes.remove(operator.index-1);
// two items were removed, so every index higher than the current one needs to be lowered by 2
for operator2 in &mut operators {
if operator2.index > operator.index {
operator2.index -= 2;
}
}
}

What is the proper way to consume pieces of a slice in a loop when the piece size can change each iteration?

I need to process a slice of bytes into fixed chunks, but the chunk pattern is only known at runtime:
pub fn process(mut message: &[u8], pattern: &[Pattern]) {
for element in pattern: {
match element {
(...) => {
let (chunk, message2) = message.split_at(element.size);
/* process chunk */
message = message2;
},
// ...
}
}
}
It feels awkward to have to use this message2. But if I do
let (chunk, message) = message.split_at(element.size);
Then it does not work, I assume because this actually creates a new messages binding that goes out of scope between loop iterations.
Is there a more elegant way to do this?
You are correct in your reasoning that let (chunk, message) = message.split_at(element.size); creates a new binding message within that scope and does not update the outer message value.
What you are looking for is a 'destructuring assignment' of the tuple. This would allow tuple elements to be assigned to existing variable bindings instead of creating new bindings, something like:
let chunk;
(chunk, message) = message.split_at(element.size);
Unfortunately this is currently not possible in Rust. You can see a pre-RFC which proposes to add destructuring assignment to the Rust language.
I believe what you currently have is a perfectly fine solution, perhaps rename message2 to something like rest_of_message or remaining_message.

Resources