Merge pull request #5317 from Feoramund/fixup-container-queue

Tidy up `core:container/queue`
This commit is contained in:
Jeroen van Rijn
2025-06-12 00:00:34 +02:00
committed by GitHub
2 changed files with 439 additions and 58 deletions
+276 -52
View File
@@ -4,7 +4,13 @@ import "base:builtin"
import "base:runtime" import "base:runtime"
_ :: runtime _ :: runtime
// Dynamically resizable double-ended queue/ring-buffer /*
`Queue` is a dynamically resizable double-ended queue/ring-buffer.
Being double-ended means that either end may be pushed onto or popped from
across the same block of memory, in any order, thus providing both stack and
queue-like behaviors in the same data structure.
*/
Queue :: struct($T: typeid) { Queue :: struct($T: typeid) {
data: [dynamic]T, data: [dynamic]T,
len: uint, len: uint,
@@ -13,18 +19,31 @@ Queue :: struct($T: typeid) {
DEFAULT_CAPACITY :: 16 DEFAULT_CAPACITY :: 16
// Procedure to initialize a queue /*
Initialize a `Queue` with a starting `capacity` and an `allocator`.
*/
init :: proc(q: ^$Q/Queue($T), capacity := DEFAULT_CAPACITY, allocator := context.allocator) -> runtime.Allocator_Error { init :: proc(q: ^$Q/Queue($T), capacity := DEFAULT_CAPACITY, allocator := context.allocator) -> runtime.Allocator_Error {
if q.data.allocator.procedure == nil {
q.data.allocator = allocator
}
clear(q) clear(q)
q.data = transmute([dynamic]T)runtime.Raw_Dynamic_Array{
data = nil,
len = 0,
cap = 0,
allocator = allocator,
}
return reserve(q, capacity) return reserve(q, capacity)
} }
// Procedure to initialize a queue from a fixed backing slice. /*
// The contents of the `backing` will be overwritten as items are pushed onto the `Queue`. Initialize a `Queue` from a fixed `backing` slice into which modifications are
// Any previous contents are not available. made directly.
The contents of the `backing` will be overwritten as items are pushed onto the
`Queue`. Any previous contents will not be available through the API but are
not explicitly zeroed either.
Note that procedures which need space to work (`push_back`, ...) will fail if
the backing slice runs out of space.
*/
init_from_slice :: proc(q: ^$Q/Queue($T), backing: []T) -> bool { init_from_slice :: proc(q: ^$Q/Queue($T), backing: []T) -> bool {
clear(q) clear(q)
q.data = transmute([dynamic]T)runtime.Raw_Dynamic_Array{ q.data = transmute([dynamic]T)runtime.Raw_Dynamic_Array{
@@ -36,8 +55,14 @@ init_from_slice :: proc(q: ^$Q/Queue($T), backing: []T) -> bool {
return true return true
} }
// Procedure to initialize a queue from a fixed backing slice. /*
// Existing contents are preserved and available on the queue. Initialize a `Queue` from a fixed `backing` slice into which modifications are
made directly.
The contents of the queue will start out with all of the elements in `backing`,
effectively creating a full queue from the slice. As such, no procedures will
be able to add more elements to the queue until some are taken off.
*/
init_with_contents :: proc(q: ^$Q/Queue($T), backing: []T) -> bool { init_with_contents :: proc(q: ^$Q/Queue($T), backing: []T) -> bool {
clear(q) clear(q)
q.data = transmute([dynamic]T)runtime.Raw_Dynamic_Array{ q.data = transmute([dynamic]T)runtime.Raw_Dynamic_Array{
@@ -50,27 +75,45 @@ init_with_contents :: proc(q: ^$Q/Queue($T), backing: []T) -> bool {
return true return true
} }
// Procedure to destroy a queue /*
Delete memory that has been dynamically allocated from a `Queue` that was setup with `init`.
Note that this procedure should not be used on queues setup with
`init_from_slice` or `init_with_contents`, as neither of those procedures keep
track of the allocator state of the underlying `backing` slice.
*/
destroy :: proc(q: ^$Q/Queue($T)) { destroy :: proc(q: ^$Q/Queue($T)) {
delete(q.data) delete(q.data)
} }
// The length of the queue /*
Return the length of the queue.
*/
len :: proc(q: $Q/Queue($T)) -> int { len :: proc(q: $Q/Queue($T)) -> int {
return int(q.len) return int(q.len)
} }
// The current capacity of the queue /*
Return the capacity of the queue.
*/
cap :: proc(q: $Q/Queue($T)) -> int { cap :: proc(q: $Q/Queue($T)) -> int {
return builtin.len(q.data) return builtin.len(q.data)
} }
// Remaining space in the queue (cap-len) /*
Return the remaining space in the queue.
This will be `cap() - len()`.
*/
space :: proc(q: $Q/Queue($T)) -> int { space :: proc(q: $Q/Queue($T)) -> int {
return builtin.len(q.data) - int(q.len) return builtin.len(q.data) - int(q.len)
} }
// Reserve enough space for at least the specified capacity /*
Reserve enough space in the queue for at least the specified capacity.
This may return an error if allocation failed.
*/
reserve :: proc(q: ^$Q/Queue($T), capacity: int) -> runtime.Allocator_Error { reserve :: proc(q: ^$Q/Queue($T), capacity: int) -> runtime.Allocator_Error {
if capacity > space(q^) { if capacity > space(q^) {
return _grow(q, uint(capacity)) return _grow(q, uint(capacity))
@@ -78,56 +121,154 @@ reserve :: proc(q: ^$Q/Queue($T), capacity: int) -> runtime.Allocator_Error {
return nil return nil
} }
/*
Shrink a queue's dynamically allocated array.
This has no effect if the queue was initialized with a backing slice.
*/
shrink :: proc(q: ^$Q/Queue($T), temp_allocator := context.temp_allocator, loc := #caller_location) {
if q.data.allocator.procedure == runtime.nil_allocator_proc {
return
}
if q.len > 0 && q.offset > 0 {
// Make the array contiguous again.
buffer := make([]T, q.len, temp_allocator)
defer delete(buffer, temp_allocator)
right := uint(builtin.len(q.data)) - q.offset
copy(buffer[:], q.data[q.offset:])
copy(buffer[right:], q.data[:q.offset])
copy(q.data[:], buffer[:])
q.offset = 0
}
builtin.shrink(&q.data, q.len, loc)
}
/*
Get the element at index `i`.
This will raise a bounds checking error if `i` is an invalid index.
*/
get :: proc(q: ^$Q/Queue($T), #any_int i: int, loc := #caller_location) -> T { get :: proc(q: ^$Q/Queue($T), #any_int i: int, loc := #caller_location) -> T {
runtime.bounds_check_error_loc(loc, i, builtin.len(q.data)) runtime.bounds_check_error_loc(loc, i, int(q.len))
idx := (uint(i)+q.offset)%builtin.len(q.data) idx := (uint(i)+q.offset)%builtin.len(q.data)
return q.data[idx] return q.data[idx]
} }
front :: proc(q: ^$Q/Queue($T)) -> T { /*
return q.data[q.offset] Get a pointer to the element at index `i`.
}
front_ptr :: proc(q: ^$Q/Queue($T)) -> ^T {
return &q.data[q.offset]
}
back :: proc(q: ^$Q/Queue($T)) -> T { This will raise a bounds checking error if `i` is an invalid index.
idx := (q.offset+uint(q.len - 1))%builtin.len(q.data) */
return q.data[idx] get_ptr :: proc(q: ^$Q/Queue($T), #any_int i: int, loc := #caller_location) -> ^T {
} runtime.bounds_check_error_loc(loc, i, int(q.len))
back_ptr :: proc(q: ^$Q/Queue($T)) -> ^T {
idx := (q.offset+uint(q.len - 1))%builtin.len(q.data) idx := (uint(i)+q.offset)%builtin.len(q.data)
return &q.data[idx] return &q.data[idx]
} }
/*
Set the element at index `i` to `val`.
This will raise a bounds checking error if `i` is an invalid index.
*/
set :: proc(q: ^$Q/Queue($T), #any_int i: int, val: T, loc := #caller_location) { set :: proc(q: ^$Q/Queue($T), #any_int i: int, val: T, loc := #caller_location) {
runtime.bounds_check_error_loc(loc, i, builtin.len(q.data)) runtime.bounds_check_error_loc(loc, i, int(q.len))
idx := (uint(i)+q.offset)%builtin.len(q.data) idx := (uint(i)+q.offset)%builtin.len(q.data)
q.data[idx] = val q.data[idx] = val
} }
get_ptr :: proc(q: ^$Q/Queue($T), #any_int i: int, loc := #caller_location) -> ^T {
runtime.bounds_check_error_loc(loc, i, builtin.len(q.data))
idx := (uint(i)+q.offset)%builtin.len(q.data) /*
Get the element at the front of the queue.
This will raise a bounds checking error if the queue is empty.
*/
front :: proc(q: ^$Q/Queue($T), loc := #caller_location) -> T {
when !ODIN_NO_BOUNDS_CHECK {
ensure(q.len > 0, "Queue is empty.", loc)
}
return q.data[q.offset]
}
/*
Get a pointer to the element at the front of the queue.
This will raise a bounds checking error if the queue is empty.
*/
front_ptr :: proc(q: ^$Q/Queue($T), loc := #caller_location) -> ^T {
when !ODIN_NO_BOUNDS_CHECK {
ensure(q.len > 0, "Queue is empty.", loc)
}
return &q.data[q.offset]
}
/*
Get the element at the back of the queue.
This will raise a bounds checking error if the queue is empty.
*/
back :: proc(q: ^$Q/Queue($T), loc := #caller_location) -> T {
when !ODIN_NO_BOUNDS_CHECK {
ensure(q.len > 0, "Queue is empty.", loc)
}
idx := (q.offset+uint(q.len - 1))%builtin.len(q.data)
return q.data[idx]
}
/*
Get a pointer to the element at the back of the queue.
This will raise a bounds checking error if the queue is empty.
*/
back_ptr :: proc(q: ^$Q/Queue($T), loc := #caller_location) -> ^T {
when !ODIN_NO_BOUNDS_CHECK {
ensure(q.len > 0, "Queue is empty.", loc)
}
idx := (q.offset+uint(q.len - 1))%builtin.len(q.data)
return &q.data[idx] return &q.data[idx]
} }
@(deprecated="Use `front_ptr` instead")
peek_front :: proc(q: ^$Q/Queue($T), loc := #caller_location) -> ^T { peek_front :: proc(q: ^$Q/Queue($T), loc := #caller_location) -> ^T {
runtime.bounds_check_error_loc(loc, 0, builtin.len(q.data)) return front_ptr(q, loc)
idx := q.offset%builtin.len(q.data)
return &q.data[idx]
} }
@(deprecated="Use `back_ptr` instead")
peek_back :: proc(q: ^$Q/Queue($T), loc := #caller_location) -> ^T { peek_back :: proc(q: ^$Q/Queue($T), loc := #caller_location) -> ^T {
runtime.bounds_check_error_loc(loc, int(q.len - 1), builtin.len(q.data)) return back_ptr(q, loc)
idx := (uint(q.len - 1)+q.offset)%builtin.len(q.data)
return &q.data[idx]
} }
// Push an element to the back of the queue /*
Push an element to the back of the queue.
If there is no more space left and allocation fails to get more, this will
return false with an `Allocator_Error`.
Example:
import "base:runtime"
import "core:container/queue"
// This demonstrates typical queue behavior (First-In First-Out).
main :: proc() {
q: queue.Queue(int)
queue.init(&q)
queue.push_back(&q, 1)
queue.push_back(&q, 2)
queue.push_back(&q, 3)
// q.data is now [1, 2, 3, ...]
assert(queue.pop_front(&q) == 1)
assert(queue.pop_front(&q) == 2)
assert(queue.pop_front(&q) == 3)
}
*/
push_back :: proc(q: ^$Q/Queue($T), elem: T) -> (ok: bool, err: runtime.Allocator_Error) { push_back :: proc(q: ^$Q/Queue($T), elem: T) -> (ok: bool, err: runtime.Allocator_Error) {
if space(q^) == 0 { if space(q^) == 0 {
_grow(q) or_return _grow(q) or_return
@@ -138,7 +279,30 @@ push_back :: proc(q: ^$Q/Queue($T), elem: T) -> (ok: bool, err: runtime.Allocato
return true, nil return true, nil
} }
// Push an element to the front of the queue /*
Push an element to the front of the queue.
If there is no more space left and allocation fails to get more, this will
return false with an `Allocator_Error`.
Example:
import "base:runtime"
import "core:container/queue"
// This demonstrates stack behavior (First-In Last-Out).
main :: proc() {
q: queue.Queue(int)
queue.init(&q)
queue.push_back(&q, 1)
queue.push_back(&q, 2)
queue.push_back(&q, 3)
// q.data is now [1, 2, 3, ...]
assert(queue.pop_back(&q) == 3)
assert(queue.pop_back(&q) == 2)
assert(queue.pop_back(&q) == 1)
}
*/
push_front :: proc(q: ^$Q/Queue($T), elem: T) -> (ok: bool, err: runtime.Allocator_Error) { push_front :: proc(q: ^$Q/Queue($T), elem: T) -> (ok: bool, err: runtime.Allocator_Error) {
if space(q^) == 0 { if space(q^) == 0 {
_grow(q) or_return _grow(q) or_return
@@ -149,16 +313,44 @@ push_front :: proc(q: ^$Q/Queue($T), elem: T) -> (ok: bool, err: runtime.Allocat
return true, nil return true, nil
} }
/*
Pop an element from the back of the queue.
// Pop an element from the back of the queue This will raise a bounds checking error if the queue is empty.
Example:
import "base:runtime"
import "core:container/queue"
// This demonstrates stack behavior (First-In Last-Out) at the far end of the data array.
main :: proc() {
q: queue.Queue(int)
queue.init(&q)
queue.push_front(&q, 1)
queue.push_front(&q, 2)
queue.push_front(&q, 3)
// q.data is now [..., 3, 2, 1]
log.infof("%#v", q)
assert(queue.pop_front(&q) == 3)
assert(queue.pop_front(&q) == 2)
assert(queue.pop_front(&q) == 1)
}
*/
pop_back :: proc(q: ^$Q/Queue($T), loc := #caller_location) -> (elem: T) { pop_back :: proc(q: ^$Q/Queue($T), loc := #caller_location) -> (elem: T) {
assert(condition=q.len > 0, loc=loc) when !ODIN_NO_BOUNDS_CHECK {
ensure(q.len > 0, "Queue is empty.", loc)
}
q.len -= 1 q.len -= 1
idx := (q.offset+uint(q.len))%builtin.len(q.data) idx := (q.offset+uint(q.len))%builtin.len(q.data)
elem = q.data[idx] elem = q.data[idx]
return return
} }
// Safely pop an element from the back of the queue
/*
Pop an element from the back of the queue if one exists and return true.
Otherwise, return a nil element and false.
*/
pop_back_safe :: proc(q: ^$Q/Queue($T)) -> (elem: T, ok: bool) { pop_back_safe :: proc(q: ^$Q/Queue($T)) -> (elem: T, ok: bool) {
if q.len > 0 { if q.len > 0 {
q.len -= 1 q.len -= 1
@@ -169,15 +361,25 @@ pop_back_safe :: proc(q: ^$Q/Queue($T)) -> (elem: T, ok: bool) {
return return
} }
// Pop an element from the front of the queue /*
Pop an element from the front of the queue
This will raise a bounds checking error if the queue is empty.
*/
pop_front :: proc(q: ^$Q/Queue($T), loc := #caller_location) -> (elem: T) { pop_front :: proc(q: ^$Q/Queue($T), loc := #caller_location) -> (elem: T) {
assert(condition=q.len > 0, loc=loc) when !ODIN_NO_BOUNDS_CHECK {
ensure(q.len > 0, "Queue is empty.", loc)
}
elem = q.data[q.offset] elem = q.data[q.offset]
q.offset = (q.offset+1)%builtin.len(q.data) q.offset = (q.offset+1)%builtin.len(q.data)
q.len -= 1 q.len -= 1
return return
} }
// Safely pop an element from the front of the queue
/*
Pop an element from the front of the queue if one exists and return true.
Otherwise, return a nil element and false.
*/
pop_front_safe :: proc(q: ^$Q/Queue($T)) -> (elem: T, ok: bool) { pop_front_safe :: proc(q: ^$Q/Queue($T)) -> (elem: T, ok: bool) {
if q.len > 0 { if q.len > 0 {
elem = q.data[q.offset] elem = q.data[q.offset]
@@ -188,7 +390,12 @@ pop_front_safe :: proc(q: ^$Q/Queue($T)) -> (elem: T, ok: bool) {
return return
} }
// Push multiple elements to the back of the queue /*
Push many elements at once to the back of the queue.
If there is not enough space left and allocation fails to get more, this will
return false with an `Allocator_Error`.
*/
push_back_elems :: proc(q: ^$Q/Queue($T), elems: ..T) -> (ok: bool, err: runtime.Allocator_Error) { push_back_elems :: proc(q: ^$Q/Queue($T), elems: ..T) -> (ok: bool, err: runtime.Allocator_Error) {
n := uint(builtin.len(elems)) n := uint(builtin.len(elems))
if space(q^) < int(n) { if space(q^) < int(n) {
@@ -207,9 +414,15 @@ push_back_elems :: proc(q: ^$Q/Queue($T), elems: ..T) -> (ok: bool, err: runtime
return true, nil return true, nil
} }
// Consume `n` elements from the front of the queue /*
Consume `n` elements from the back of the queue.
This will raise a bounds checking error if the queue does not have enough elements.
*/
consume_front :: proc(q: ^$Q/Queue($T), n: int, loc := #caller_location) { consume_front :: proc(q: ^$Q/Queue($T), n: int, loc := #caller_location) {
assert(condition=int(q.len) >= n, loc=loc) when !ODIN_NO_BOUNDS_CHECK {
ensure(q.len >= uint(n), "Queue does not have enough elements to consume.", loc)
}
if n > 0 { if n > 0 {
nu := uint(n) nu := uint(n)
q.offset = (q.offset + nu) % builtin.len(q.data) q.offset = (q.offset + nu) % builtin.len(q.data)
@@ -217,9 +430,15 @@ consume_front :: proc(q: ^$Q/Queue($T), n: int, loc := #caller_location) {
} }
} }
// Consume `n` elements from the back of the queue /*
Consume `n` elements from the back of the queue.
This will raise a bounds checking error if the queue does not have enough elements.
*/
consume_back :: proc(q: ^$Q/Queue($T), n: int, loc := #caller_location) { consume_back :: proc(q: ^$Q/Queue($T), n: int, loc := #caller_location) {
assert(condition=int(q.len) >= n, loc=loc) when !ODIN_NO_BOUNDS_CHECK {
ensure(q.len >= uint(n), "Queue does not have enough elements to consume.", loc)
}
if n > 0 { if n > 0 {
q.len -= uint(n) q.len -= uint(n)
} }
@@ -231,9 +450,14 @@ append_elem :: push_back
append_elems :: push_back_elems append_elems :: push_back_elems
push :: proc{push_back, push_back_elems} push :: proc{push_back, push_back_elems}
append :: proc{push_back, push_back_elems} append :: proc{push_back, push_back_elems}
enqueue :: push_back
dequeue :: pop_front
// Clear the contents of the queue /*
Reset the queue's length and offset to zero, letting it write new elements over
old memory, in effect clearing the accessible contents.
*/
clear :: proc(q: ^$Q/Queue($T)) { clear :: proc(q: ^$Q/Queue($T)) {
q.len = 0 q.len = 0
q.offset = 0 q.offset = 0
+157
View File
@@ -0,0 +1,157 @@
package test_core_container
import "base:runtime"
import "core:container/queue"
import "core:testing"
@test
test_queue :: proc(t: ^testing.T) {
buf := [?]int{99, 99, 99, 99, 99}
q: queue.Queue(int)
testing.expect(t, queue.init_from_slice(&q, buf[:]))
testing.expect_value(t, queue.reserve(&q, len(buf)), nil)
queue.push_back(&q, 1)
queue.push_back_elems(&q, 2, 3)
queue.push_front(&q, 0)
// {
// data = [1, 2, 3, 99, 0],
// len = 4,
// offset = 4,
// }
testing.expect_value(t, queue.back(&q), 3)
testing.expect_value(t, queue.back_ptr(&q), &buf[2])
testing.expect_value(t, queue.front(&q), 0)
testing.expect_value(t, queue.front_ptr(&q), &buf[4])
queue.get(&q, 3)
for i in 0..<4 {
testing.expect_value(t, queue.get(&q, i), i)
queue.set(&q, i, i)
}
testing.expect_value(t, queue.get_ptr(&q, 3), &buf[2])
queue.consume_back(&q, 1)
queue.consume_front(&q, 1)
testing.expect_value(t, queue.pop_back(&q), 2)
v, ok := queue.pop_back_safe(&q)
testing.expect_value(t, v, 1)
testing.expect_value(t, ok, true)
// Test `init_with_contents`.
buf2 := [?]int{99, 3, 5}
queue.init_with_contents(&q, buf2[:])
push_ok, push_err := queue.push_back(&q, 1)
testing.expect(t, !push_ok)
testing.expect_value(t, push_err, runtime.Allocator_Error.Out_Of_Memory)
push_ok, push_err = queue.push_front(&q, 2)
testing.expect(t, !push_ok)
testing.expect_value(t, push_err, runtime.Allocator_Error.Out_Of_Memory)
pop_front_v, pop_front_ok := queue.pop_front_safe(&q)
testing.expect(t, pop_front_ok)
testing.expect_value(t, pop_front_v, 99)
// Re-initialization.
queue.init(&q, 0)
defer queue.destroy(&q)
queue.push_back_elems(&q, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18)
testing.expect_value(t, queue.len(q), 18)
queue.push_back_elems(&q, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18)
testing.expect_value(t, queue.len(q), 36)
for i in 1..=18 {
testing.expect_value(t, queue.pop_front(&q), i)
}
for i in 1..=18 {
testing.expect_value(t, queue.pop_front(&q), i)
}
}
@test
test_queue_grow_edge_case :: proc(t: ^testing.T) {
// Create a situation in which we trigger `q.offset + q.len > n` inside
// `_grow` to evaluate the `copy` behavior.
qq: queue.Queue(int)
queue.init(&qq, 0)
defer queue.destroy(&qq)
queue.push_back_elems(&qq, 1, 2, 3, 4, 5, 6, 7)
testing.expect_value(t, queue.pop_front(&qq), 1)
testing.expect_value(t, queue.pop_front(&qq), 2)
testing.expect_value(t, queue.pop_front(&qq), 3)
queue.push_back(&qq, 8)
queue.push_back(&qq, 9)
testing.expect_value(t, qq.len, 6)
testing.expect_value(t, qq.offset, 3)
testing.expect_value(t, len(qq.data), 8) // value contingent on smallest dynamic array capacity on first allocation
queue.reserve(&qq, 16)
testing.expect_value(t, queue.len(qq), 6)
for i in 4..=9 {
testing.expect_value(t, queue.pop_front(&qq), i)
}
testing.expect_value(t, queue.len(qq), 0)
// If we made it to this point without failure, the queue should have
// copied the data into the right place after resizing the backing array.
}
@test
test_queue_grow_edge_case_2 :: proc(t: ^testing.T) {
// Create a situation in which we trigger `insert_from + insert_to > sz` inside `push_back_elems`
// to evaluate the modified `insert_to` behavior.
qq: queue.Queue(int)
queue.init(&qq, 8)
defer queue.destroy(&qq)
queue.push_back_elems(&qq, -1, -2, -3, -4, -5, -6, -7)
queue.consume_front(&qq, 3)
queue.push_back_elems(&qq, -8, -9, -10)
queue.push_back_elems(&qq, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
testing.expect_value(t, queue.len(qq), 17)
for i in 4..=10 {
testing.expect_value(t, queue.pop_front(&qq), -i)
}
for i in 1..=10 {
testing.expect_value(t, queue.pop_front(&qq), i)
}
testing.expect_value(t, queue.len(qq), 0)
}
@test
test_queue_shrink :: proc(t: ^testing.T) {
qq: queue.Queue(int)
queue.init(&qq, 8)
defer queue.destroy(&qq)
queue.push_back_elems(&qq, -1, -2, -3, -4, -5, -6, -7)
queue.consume_front(&qq, 3)
queue.push_back_elems(&qq, -8, -9, -10)
queue.push_back_elems(&qq, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
queue.shrink(&qq)
queue.consume_front(&qq, 7)
queue.shrink(&qq)
for i in 1..=10 {
testing.expect_value(t, queue.pop_front(&qq), i)
}
buf: [1]int
qq_backed: queue.Queue(int)
queue.init_from_slice(&qq_backed, buf[:])
queue.shrink(&qq_backed)
}