1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
//! Raylib-managed memory. Similar in function to [`Box`], but specifically
//! designed for `RL_MALLOC`/`RL_REALLOC`/`RL_FREE`.
//!
//! See [`DataBuf`]
#![warn(clippy::style, clippy::pedantic, clippy::perf)]
#![allow(
clippy::missing_errors_doc,
reason = "errors are documented at their definition, not by the functions that use them"
)]
#![warn(
clippy::unnecessary_safety_comment,
clippy::unnecessary_safety_doc,
reason = "minimize confusion over what is and isn't safe"
)]
#![deny(
clippy::missing_safety_doc,
clippy::undocumented_unsafe_blocks,
clippy::multiple_unsafe_ops_per_block,
reason = "DataBuf is delicate and assumptions must be clearly declared and scoped"
)]
//! Data manipulation functions. Compress and Decompress with DEFLATE
use std::{
alloc::Layout,
marker::PhantomData,
mem::MaybeUninit,
num::{NonZeroU32, NonZeroUsize},
ops::{Deref, DerefMut},
ptr::NonNull,
};
use crate::{error::AllocationError, ffi};
/// Calculate the number of bytes needed to allocate `layout`.
#[inline]
fn allocation_layout_size(layout: Layout) -> Result<NonZeroU32, AllocationError> {
let bytes = layout
.size()
.try_into()
.ok()
.ok_or(AllocationError::IntoUIntFailed)?;
NonZeroU32::new(bytes).ok_or(AllocationError::ZeroBytes)
}
/// Calculate the number of bytes needed to allocate `[T; count]`.
#[inline]
fn allocation_array_size<T>(count: usize) -> Result<NonZeroU32, AllocationError> {
allocation_layout_size(Layout::array::<T>(count).map_err(|_| AllocationError::IntoUIntFailed)?)
}
/// Calculate the number of bytes needed to allocate a copy of `val`
#[inline]
fn allocation_val_size<T: ?Sized>(val: &T) -> Result<NonZeroU32, AllocationError> {
allocation_layout_size(Layout::for_value(val))
}
mod rl_managed {
use super::ffi;
use std::{mem::MaybeUninit, num::NonZeroU32, ptr::NonNull};
/// Raylib-managed [`NonNull`].
///
/// # Safety
///
/// [`RlManaged`] must be unique, not dangling, and allocated with `RL_ALLOC`/[`ffi::MemAlloc`]
/// or `RL_REALLOC`/[`ffi::MemRealloc`].
///
/// **However**, it is *not* guaranteed to be safe to dereference. It is the user's responsibility
/// to ensure the pointer stored in [`RlManaged`] is safe to dereference as the type it claims to
/// be, for the count it claims to be, before dereferencing.
///
/// [`RlManaged`] does not guarantee the size, alignment, nor validity of its allocation -- **it
/// only guarantees that its memory block is safe for Raylib to re/deallocate**.
#[repr(transparent)]
#[derive(Debug)]
#[must_use]
pub struct RlManaged<T: ?Sized>(/* unsafe */ NonNull<T>);
/// Allocate `size` bytes of memory aligned to `T` using [`ffi::MemAlloc`].
///
/// Returns [`None`] if [`ffi::MemAlloc`] returned null.
#[inline]
pub fn mem_alloc<T>(size: NonZeroU32) -> Option<RlManaged<MaybeUninit<T>>> {
// SAFETY: `size` is not zero.
let ptr = unsafe { ffi::MemAlloc(size.get()) }.cast();
NonNull::new(ptr).map(RlManaged)
}
impl<T: ?Sized> RlManaged<T> {
/// Mark memory as Raylib-managed.
///
/// # Safety
///
/// `data` must be unique, not dangling, and allocated with `RL_ALLOC`/[`ffi::MemAlloc`] or `RL_REALLOC`/[`ffi::MemRealloc`].
#[inline]
pub(crate) const unsafe fn new(data: NonNull<T>) -> Self {
Self(data)
}
/// Returns a shared reference to the value.
///
/// # Safety
///
/// `self` must be safe to dereference as `T`.
/// See [`RlManaged`] safety for more information.
#[inline]
#[must_use]
pub const unsafe fn as_ref(&self) -> &T {
// SAFETY: RlManaged must be unique and cannot dangle.
// Taking `self` by reference guarantees aliasing rules are followed.
// Caller must ensure `self` is safe to dereference.
unsafe { self.0.as_ref() }
}
/// Returns a unique reference to the value.
///
/// # Safety
///
/// `self` must be safe to dereference as `T`.
/// See [`RlManaged`] safety for more information.
#[inline]
#[must_use]
pub const unsafe fn as_mut(&mut self) -> &mut T {
// SAFETY: RlManaged must be unique and cannot dangle.
// Taking `self` by mutable reference guarantees aliasing rules are followed.
// Caller must ensure `self` is safe to dereference.
unsafe { self.0.as_mut() }
}
/// Access the pointer of this allocation.
#[inline]
#[must_use]
pub const fn into_inner(self) -> NonNull<T> {
self.0
}
/// Reallocate `self` to have `size` bytes of memory aligned to `U` using [`ffi::MemRealloc`].
///
/// Any elements that were initialized prior to calling will still be initialized after reallocating.
/// Any elements that were not previously in the allocation are uninitialized.
///
/// Returns the original memory block if [`ffi::MemRealloc`] returned null.
///
/// **WARNING:** This method does not drop the contents of `self`.
#[inline]
pub fn mem_realloc<U>(self, size: NonZeroU32) -> Result<RlManaged<MaybeUninit<U>>, Self> {
// SAFETY: `self` is non-null and is Raylib-allocated, and `size` is not zero.
let new_ptr = unsafe { ffi::MemRealloc(self.0.as_ptr().cast(), size.get()) }.cast();
NonNull::new(new_ptr).map(RlManaged).ok_or(self)
}
/// Free `self` using [`ffi::MemFree`].
///
/// **WARNING:** This method does not drop the contents of `self`.
#[inline]
pub fn mem_free(self) {
// SAFETY: `self` is non-null, not dangling, and Raylib-allocated.
unsafe {
ffi::MemFree(self.0.as_ptr().cast());
}
}
}
impl<T> RlManaged<[T]> {
/// Create a Raylib-managed slice from a thin pointer and a length.
///
/// The `len` argument is the number of **elements**, not the number of bytes.
///
/// This function is safe, but dereferencing the return value is unsafe.
/// See the documentation of [`std::slice::from_raw_parts`] for slice safety requirements.
#[inline]
#[allow(
clippy::needless_pass_by_value,
reason = "passing by reference would allow `data` to be duplicated because `data.0` is Copy"
)]
pub(crate) const fn slice_from_raw_parts(data: RlManaged<T>, len: usize) -> Self {
Self(NonNull::slice_from_raw_parts(data.0, len))
}
}
}
pub use rl_managed::*;
/// A wrapper acting as an owned buffer for Raylib-allocated memory.
/// Automatically releases the memory with [`ffi::MemFree()`] when dropped.
///
/// # Example
/// ```
/// use raylib::prelude::*;
/// let buf: DataBuf<[u8]> = compress_data(b"11111").unwrap();
/// // Use this how you used to use the return of `compress_data()`.
/// // It will live until `buf` goes out of scope or gets dropped.
/// let data: &[u8] = buf.as_ref();
/// let expected: &[u8] = &[1, 5, 0, 250, 255, 49, 49, 49, 49, 49];
/// assert_eq!(data, expected);
/// ```
///
/// # Safety
///
/// - `buf` must not be dangling.
/// - `buf` must be safe to dereference.
/// - `buf` must be a **unique, owned** pointer (not to static or local memory, and the memory must not be
/// accessible through any pointers/references not derived from the returned [`DataBuf`]).
/// - `buf` must point to [valid](https://doc.rust-lang.org/std/ptr/index.html#safety), initialized data.
/// - `buf` must be [convertible to a reference](std::ptr#pointer-to-reference-conversion).
/// - `buf` must have been created with `RL_MALLOC`/[`ffi::MemAlloc`] or `RL_REALLOC`/[`ffi::MemRealloc`].
///
/// This structure is only intended for use with pointers given by Raylib with the expectation that you
/// would manually deallocate them with [`ffi::MemFree`]. DO NOT use this structure to hold arbitrary
/// or un-owned pointers.
///
/// If the pointer is expected to be conditionally deallocated by Raylib,
/// (i.e. conditionally passing the buffer to a Raylib function that will certainly deallocatate it)
/// use [`DataBuf::into_inner`] to prevent the [`Drop`] impl from causing a double-free.
#[derive(Debug)]
#[repr(transparent)]
#[must_use]
pub struct DataBuf<T: ?Sized> {
buf: RlManaged<T>,
/// Tell the compiler that this instance logically owns a `T`.
_marker: PhantomData<T>,
}
impl<T: ?Sized> Drop for DataBuf<T> {
#[inline]
fn drop(&mut self) {
let mut ptr = MaybeUninit::uninit();
// SAFETY: Both `self.buf` and `ptr` are non-null and valid for 1 element.
// Taking `self` by mutable reference ensures aliasing rules are upheld
// outside of the method; and `self` is not used again after this line.
// Because `drop` is the end of `self`'s lifetime, `buf` is guaranteed not
// to be accessed again after the function returns.
unsafe {
std::ptr::copy_nonoverlapping(std::ptr::from_ref(&self.buf), ptr.as_mut_ptr(), 1);
}
// SAFETY: Just written to with a valid value.
let data = unsafe { ptr.assume_init() }.into_inner();
// SAFETY: DataBuf `buf` is guaranteed to be unique, owned, non-null, valid, and not dangling.
// DataBuf and RlManaged do not implement Clone, and `drop` is called *at most once*, so `data`
// is guaranteed not to have been dropped for `T` yet so long as DataBuf's safety contract has
// been upheld.
unsafe {
data.drop_in_place();
}
// SAFETY: `data` taken from `RlManaged` is still managed by Raylib after contents is dropped.
// Any copies of `data` go out of scope, preventing double-free.
unsafe { RlManaged::new(data) }.mem_free();
}
}
impl<T: ?Sized> Deref for DataBuf<T> {
type Target = T;
#[inline]
fn deref(&self) -> &Self::Target {
self.as_ref()
}
}
impl<T: ?Sized> DerefMut for DataBuf<T> {
#[inline]
fn deref_mut(&mut self) -> &mut Self::Target {
self.as_mut()
}
}
impl<T: ?Sized> AsRef<T> for DataBuf<T> {
#[inline]
fn as_ref(&self) -> &T {
self
}
}
impl<T: ?Sized> AsMut<T> for DataBuf<T> {
#[inline]
fn as_mut(&mut self) -> &mut T {
self
}
}
impl<T> DataBuf<MaybeUninit<T>> {
/// Initialize the buffer with valid memory.
#[inline]
pub const fn write(mut self, val: T) -> DataBuf<T> {
// SAFETY: DataBuf guarantees `buf` is safe to dereference, and
// ownership of `self` ensures aliasing rules are followed.
let buf = unsafe { self.buf.as_mut() };
MaybeUninit::write(buf, val);
// SAFETY: We just initialized this value.
unsafe { self.assume_init() }
}
/// Mark that the data pointed to by `self` is initialized.
///
/// # Safety
///
/// The data pointed to by `self` must actually be initialized.
#[inline]
pub const unsafe fn assume_init(self) -> DataBuf<T> {
// SAFETY: `DataBuf<MaybeUninit<T>>` and `DataBuf<T>` have the same layout
unsafe { std::mem::transmute::<DataBuf<MaybeUninit<T>>, DataBuf<T>>(self) }
}
}
impl<T> DataBuf<[MaybeUninit<T>]> {
/// Mark that the data pointed to by `self` is initialized.
///
/// # Safety
///
/// The data pointed to by `self` must actually be initialized.
#[inline]
pub const unsafe fn assume_init(self) -> DataBuf<[T]> {
// SAFETY: `DataBuf<[MaybeUninit<T>]>` and `DataBuf<[T]>` have the same layout
unsafe { std::mem::transmute::<DataBuf<[MaybeUninit<T>]>, DataBuf<[T]>>(self) }
}
}
impl<T: ?Sized> DataBuf<T> {
/// Returns a shared reference to the value.
#[inline]
#[must_use]
pub const fn as_ref(&self) -> &T {
// SAFETY: DataBuf guarantees `buf` is safe to dereference, and taking
// `self` by reference ensures aliasing rules are followed.
unsafe { self.buf.as_ref() }
}
/// Returns a unique reference to the value.
#[inline]
#[must_use]
pub const fn as_mut(&mut self) -> &mut T {
// SAFETY: DataBuf guarantees `buf` is safe to dereference, and taking
// `self` by mutable reference ensures aliasing rules are followed.
unsafe { self.buf.as_mut() }
}
/// Wrap an already allocated, non-null, Raylib-managed pointer in a [`DataBuf`].
#[inline]
pub(crate) const fn from_rlmanaged(buf: RlManaged<T>) -> Self {
Self {
buf,
_marker: PhantomData,
}
}
/// Wrap an already allocated, non-null pointer in a [`DataBuf`].
///
/// # Safety
///
/// See the [`DataBuf`] safety requirements.
#[inline]
pub(crate) const unsafe fn from_nonnull(data: NonNull<T>) -> Self {
// SAFETY: Caller must ensure `data` is Raylib-managed.
let buf = unsafe { RlManaged::new(data) };
Self::from_rlmanaged(buf)
}
/// Wrap an already allocated pointer in a [`DataBuf`].
/// Returns [`None`] if `buf` is null.
///
/// # Safety
///
/// See the [`DataBuf`] safety requirements.
#[inline]
pub(crate) const unsafe fn from_raw(ptr: *mut T) -> Option<Self> {
if let Some(buf) = NonNull::new(ptr) {
// SAFETY: Caller must uphold safety contract.
Some(unsafe { Self::from_nonnull(buf) })
} else {
None
}
}
/// Extract the pointer without freeing it, for the purpose of transferring ownership.
///
/// **WARNING:** The returned pointer must be unloaded manually to avoid a memory leak.
#[inline]
pub const fn into_inner(self) -> RlManaged<T> {
let mut buf = MaybeUninit::uninit();
// SAFETY: Both `self.buf` and `ptr` are non-null and valid for 1 element.
unsafe {
std::ptr::copy_nonoverlapping(std::ptr::from_ref(&self.buf), buf.as_mut_ptr(), 1);
}
// SAFETY: Just written to with a valid value
let buf = unsafe { buf.assume_init() };
std::mem::forget(self); // Prevent `self` from causing double-free
buf
}
}
impl<T> DataBuf<T> {
/// Allocate new memory managed by Raylib.
#[inline]
pub fn alloc() -> Result<DataBuf<MaybeUninit<T>>, AllocationError> {
let bytes = allocation_array_size::<T>(1)?;
let buf = mem_alloc::<T>(bytes).ok_or(AllocationError::NullAlloc)?;
Ok(DataBuf::from_rlmanaged(buf))
}
/// Allocate new memory managed by Raylib and move `val` into it.
#[inline]
pub fn alloc_from(val: T) -> Result<Self, (AllocationError, T)> {
match Self::alloc() {
Ok(buf) => Ok(buf.write(val)),
Err(e) => Err((e, val)),
}
}
/// Allocate new memory managed by Raylib and clone `val` into it.
#[inline]
pub fn alloc_from_clone(src: &T) -> Result<Self, AllocationError>
where
T: Clone,
{
Ok(Self::alloc()?.write(src.clone()))
}
/// Allocate new memory managed by Raylib and copy `val` into it.
#[inline]
pub fn alloc_from_copy(src: &T) -> Result<Self, AllocationError>
where
T: Copy,
{
Ok(Self::alloc()?.write(*src))
}
}
impl<T> DataBuf<[T]> {
/// Wrap an already allocated pointer in a [`DataBuf`].
///
/// # Safety
///
/// **In addition** to the [`DataBuf`] and [`from_raw_parts_mut`](std::slice::from_raw_parts_mut)
/// safety requirements, this function also requires:
/// - `buf` must point to an array of as many valid, initialized elements as defined by `count`.
#[inline]
pub(crate) const unsafe fn slice_from_nonnull(buf: NonNull<T>, len: NonZeroUsize) -> Self {
// SAFETY: Caller must uphold `from_raw_parts_mut` safety contract
let slice = unsafe { std::slice::from_raw_parts_mut(buf.as_ptr(), len.get()) };
// SAFETY: A mutable reference cannot be null.
let buf = unsafe { NonNull::new_unchecked(slice) };
// SAFETY: Calller must uphold `DataBuf` safety contract
unsafe { Self::from_nonnull(buf) }
}
/// Wrap an already allocated pointer in a [`DataBuf`].
/// Returns [`None`] if `buf` is null.
///
/// Takes `count` as a [`MaybeUninit<i32>`] for convenience, as most Raylib functions returning an array
/// buffer provide the length of the buffer as an [`i32`] out param.
///
/// # Safety
///
/// **In addition** to the [`DataBuf`] and [`from_raw_parts_mut`](std::slice::from_raw_parts_mut) safety requirements,
/// this function also requires:
/// - `count` must be initialized if `buf` is non-null.
/// - `buf` must point to an array of as many valid, initialized elements as defined by `count`.
///
/// # Panics
///
/// This method may panic if `count` is less than 1 or greater than [`usize::MAX`] while `buf` is non-null.
#[inline]
pub(crate) const unsafe fn slice_from_raw(
ptr: *mut T,
count: MaybeUninit<i32>,
) -> Option<Self> {
if let Some(buf) = NonNull::new(ptr) {
// SAFETY: Caller must ensure `count` is initialized if `buf` is non-null.
let count = unsafe { count.assume_init() };
assert!(count >= 1, "`count` should be positive");
// confirm `as usize` will not overflow
#[cfg(target_pointer_width = "16")]
{
assert!(
count <= usize::MAX as i32,
"`count` should fit within usize"
);
}
#[allow(clippy::cast_sign_loss, reason = "intentional")]
// SAFETY: Just checked that count is non-zero and positive.
let len = unsafe { NonZeroUsize::new_unchecked(count as usize) };
// SAFETY: Caller must uphold `DataBuf` and `slice` safety contracts
Some(unsafe { Self::slice_from_nonnull(buf, len) })
} else {
None
}
}
/// Allocate new memory managed by Raylib.
///
/// # Example
/// ```
/// # use raylib::prelude::DataBuf;
/// let mut data_buf = DataBuf::<[i32]>::alloc(5).unwrap();
/// data_buf[0].write(4);
/// data_buf[1].write(8);
/// data_buf[2].write(-23);
/// data_buf[3].write(9);
/// data_buf[4].write(0);
/// // SAFETY: Just initialized all elements
/// let data_buf = unsafe { data_buf.assume_init() };
/// assert_eq!(data_buf.as_ref(), &[4, 8, -23, 9, 0]);
/// ```
/// (See also: [`DataBuf::alloc_from_copy`])
#[inline]
pub fn alloc(count: usize) -> Result<DataBuf<[MaybeUninit<T>]>, AllocationError> {
let bytes = allocation_array_size::<T>(count)?;
let buf = mem_alloc::<T>(bytes).ok_or(AllocationError::NullAlloc)?;
Ok(DataBuf {
buf: RlManaged::slice_from_raw_parts(buf, count),
_marker: PhantomData,
})
}
/// Allocate memory managed by Raylib and initialize by cloning each element.
///
/// If a `clone()` panics partway through, the already-cloned prefix is
/// dropped and the allocation is freed before the panic propagates — no
/// leak, no drop of uninitialized memory.
///
/// # Panics
///
/// This method may panic in debug if the pointer returned by [`ffi::MemAlloc`] is unaligned.
///
/// # Example
/// ```
/// # use raylib::prelude::DataBuf;
/// let src = vec![String::from("a"), String::from("b")];
/// let data_buf = DataBuf::<[String]>::alloc_from_clone(&src).unwrap();
/// assert_eq!(data_buf.as_ref(), src.as_slice());
/// ```
pub fn alloc_from_clone(src: &[T]) -> Result<Self, AllocationError>
where
T: Clone,
{
/// Drops the initialized prefix of the buffer if a `clone()` unwinds.
struct InitGuard<'a, T> {
buf: &'a mut [MaybeUninit<T>],
init: usize,
}
impl<T> Drop for InitGuard<'_, T> {
fn drop(&mut self) {
for elem in &mut self.buf[..self.init] {
// SAFETY: the first `init` elements were initialized by
// the clone loop below before the unwind began.
unsafe { elem.assume_init_drop() };
}
}
}
let mut buf = Self::alloc(src.len())?;
let mut guard = InitGuard {
buf: buf.as_mut(),
init: 0,
};
while guard.init < src.len() {
let i = guard.init;
guard.buf[i].write(src[i].clone());
guard.init = i + 1;
}
// All elements initialized — disarm the guard (releases the borrow).
std::mem::forget(guard);
// SAFETY: the loop above initialized all `src.len()` elements.
Ok(unsafe { buf.assume_init() })
}
/// Allocate memory managed by Raylib and initialize by copying.
///
/// # Panics
///
/// This method may panic in debug if the pointer returned by [`ffi::MemAlloc`] is unaligned.
///
/// # Example
/// ```
/// # use raylib::prelude::DataBuf;
/// let src = [4, 8, -23, 9, 0];
/// let mut data_buf = DataBuf::<[i32]>::alloc_from_copy(&src).unwrap();
/// assert_eq!(data_buf.as_ref(), &src);
/// ```
pub fn alloc_from_copy(src: &[T]) -> Result<Self, AllocationError>
where
T: Copy,
{
let mut buf = Self::alloc(src.len())?;
// SAFETY: `&[T]` and `&[MaybeUninit<T>]` have the same layout. Reference to is non-null.
let uninit_src = unsafe { &*(std::ptr::from_ref::<[T]>(src) as *const [MaybeUninit<T>]) };
buf.copy_from_slice(uninit_src);
// SAFETY: Valid elements have just been copied into `self` so it is initialized.
Ok(unsafe { buf.assume_init() })
}
/// Reallocate memory already managed by Raylib.
///
/// # Panics
///
/// This method may panic in debug if the pointer returned by [`ffi::MemAlloc`] is unaligned.
pub fn realloc(
self,
new_count: usize,
) -> Result<DataBuf<[MaybeUninit<T>]>, (AllocationError, Self)> {
match allocation_array_size::<T>(new_count) {
Err(e) => Err((e, self)),
Ok(bytes) => {
let new_buf = self.into_inner().mem_realloc(bytes).map_err(|old_buf| {
(AllocationError::NullAlloc, Self::from_rlmanaged(old_buf))
})?;
let new_buf = RlManaged::slice_from_raw_parts(new_buf, new_count);
Ok(DataBuf::from_rlmanaged(new_buf))
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_drop_value() {
struct DropTest<F: FnMut()>(F);
impl<F: FnMut()> Drop for DropTest<F> {
fn drop(&mut self) {
(self.0)();
}
}
impl<F: FnMut()> std::fmt::Debug for DropTest<F> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_tuple("DropTest").finish()
}
}
let mut times_dropped = 0;
let buf = DataBuf::alloc_from(DropTest(|| times_dropped += 1)).unwrap();
drop(buf);
assert_eq!(
times_dropped, 1,
"DataBuf should drop contents exactly once"
);
}
#[test]
fn test_from_raw() {
type ExpectTy = [i32; 5];
const EXPECT: [i32; 5] = [64, 264, -57, 653, -153];
let bytes @ 1.. = (std::mem::size_of::<i32>() * EXPECT.len())
.try_into()
.unwrap()
else {
unreachable!()
};
// SAFETY: `bytes` is non-zero
let ptr = unsafe { ffi::MemAlloc(bytes) }.cast::<ExpectTy>();
assert!(!ptr.is_null(), "should be able to allocate");
// SAFETY: `ptr` is not null and `MemAlloc` returns owned memory.
unsafe {
ptr.write(EXPECT);
};
// SAFETY: `ptr` is unique, non-dangling, valid, and allocated by Raylib
let buf = unsafe { DataBuf::from_raw(ptr) }.expect("ptr should be convertible to DataBuf");
assert_eq!(&*buf, &EXPECT);
}
#[test]
fn test_slice_from_raw() {
type ExpectTy = [i32; 5];
const EXPECT: ExpectTy = [6, -453, 364, 45632, -1233];
let bytes @ 1.. = (std::mem::size_of::<i32>() * EXPECT.len())
.try_into()
.unwrap()
else {
unreachable!()
};
// SAFETY: `bytes` is non-zero
let ptr = unsafe { ffi::MemAlloc(bytes) }.cast::<i32>();
assert!(!ptr.is_null(), "should be able to allocate");
// SAFETY: `ptr` is not null and `MemAlloc` returns owned memory.
unsafe {
ptr.cast::<ExpectTy>().write(EXPECT);
};
// SAFETY: `ptr` is unique, non-dangling, valid, and allocated by Raylib
let buf = unsafe {
DataBuf::slice_from_raw(ptr, MaybeUninit::new(EXPECT.len().try_into().unwrap()))
}
.expect("ptr should be convertible to DataBuf");
assert_eq!(&*buf, &EXPECT);
}
#[test]
fn test_alloc_from_clone_non_copy() {
// A non-Copy T: this does not compile against the old `T: Copy` bound.
#[derive(Clone, PartialEq, Debug)]
struct NonCopy(String);
let src = [
NonCopy("a".into()),
NonCopy("b".into()),
NonCopy("c".into()),
];
let buf = DataBuf::<[NonCopy]>::alloc_from_clone(&src).unwrap();
assert_eq!(&*buf, &src);
}
#[test]
fn test_alloc_from_clone_panic_drops_prefix() {
use std::sync::atomic::{AtomicUsize, Ordering};
struct Bomb<'a> {
drops: &'a AtomicUsize,
clones: &'a AtomicUsize,
fuse: usize,
}
impl Clone for Bomb<'_> {
fn clone(&self) -> Self {
let n = self.clones.fetch_add(1, Ordering::SeqCst);
assert!(n + 1 != self.fuse, "boom: clone #{} hit the fuse", n + 1);
Bomb {
drops: self.drops,
clones: self.clones,
fuse: self.fuse,
}
}
}
impl Drop for Bomb<'_> {
fn drop(&mut self) {
self.drops.fetch_add(1, Ordering::SeqCst);
}
}
let drops = AtomicUsize::new(0);
let clones = AtomicUsize::new(0);
let mk = |fuse| Bomb {
drops: &drops,
clones: &clones,
fuse,
};
// Third clone panics.
let src = [mk(3), mk(3), mk(3)];
let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
DataBuf::<[Bomb]>::alloc_from_clone(&src)
}));
assert!(result.is_err(), "the clone panic must propagate");
// The 2 successfully-cloned elements must have been dropped during
// unwind (the buffer free itself is validated by the ASAN/LSAN leg).
assert_eq!(
drops.load(Ordering::SeqCst),
2,
"prefix must be dropped on unwind"
);
drop(src);
assert_eq!(
drops.load(Ordering::SeqCst),
5,
"source elements drop normally"
);
}
#[test]
fn test_alloc_zst_errors() {
// Zero-sized T → zero bytes requested → rejected before the FFI call.
let r = DataBuf::<()>::alloc();
assert!(matches!(r, Err(AllocationError::ZeroBytes)), "got {r:?}");
}
#[test]
fn test_alloc_slice_zero_len_errors() {
let r = DataBuf::<[u8]>::alloc(0);
assert!(matches!(r, Err(AllocationError::ZeroBytes)), "got {r:?}");
}
#[test]
fn test_alloc_slice_layout_overflow_errors() {
// Layout::array overflows isize::MAX → IntoUIntFailed.
let r = DataBuf::<[u64]>::alloc(usize::MAX);
assert!(
matches!(r, Err(AllocationError::IntoUIntFailed)),
"got {r:?}"
);
}
/// 64-bit only: on 32-bit targets the count expression itself would
/// overflow usize before reaching `alloc`.
#[test]
#[cfg(target_pointer_width = "64")]
fn test_alloc_slice_over_u32_max_errors() {
// Fits in usize on 64-bit but the byte size exceeds u32::MAX — the
// largest request expressible through ffi::MemAlloc(unsigned int).
let count = (u32::MAX as usize) + 1;
let r = DataBuf::<[u8]>::alloc(count);
assert!(
matches!(r, Err(AllocationError::IntoUIntFailed)),
"got {r:?}"
);
}
#[test]
fn test_alloc_large_but_valid_succeeds() {
// 1 MiB: well within u32::MAX, must succeed and be fully writable.
const MIB: usize = 1 << 20;
let buf = DataBuf::<[u8]>::alloc_from_copy(&vec![0xA5u8; MIB]).unwrap();
assert_eq!(buf.len(), MIB);
assert!(buf.iter().all(|&b| b == 0xA5));
}
#[test]
fn test_alloc_from_error_returns_value() {
// ZST → ZeroBytes; the error tuple must hand the value back intact.
#[derive(Debug, PartialEq)]
struct Zst;
let (err, val) = DataBuf::alloc_from(Zst).unwrap_err();
assert!(matches!(err, AllocationError::ZeroBytes));
assert_eq!(val, Zst);
}
#[test]
fn test_realloc_grow_preserves_prefix() {
let buf = DataBuf::<[i32]>::alloc_from_copy(&[1, 2, 3]).unwrap();
let mut grown = buf.realloc(6).map_err(|(e, _)| e).expect("realloc grow");
grown[3].write(4);
grown[4].write(5);
grown[5].write(6);
// SAFETY: indices 0..3 were initialized by alloc_from_copy and are
// preserved by mem_realloc (documented); 3..6 were just written.
let grown = unsafe { grown.assume_init() };
assert_eq!(&*grown, &[1, 2, 3, 4, 5, 6]);
}
#[test]
fn test_realloc_shrink_keeps_prefix() {
let buf = DataBuf::<[i32]>::alloc_from_copy(&[1, 2, 3, 4, 5, 6]).unwrap();
let shrunk = buf.realloc(3).map_err(|(e, _)| e).expect("realloc shrink");
// SAFETY: all 3 remaining elements were initialized before the shrink.
let shrunk = unsafe { shrunk.assume_init() };
assert_eq!(&*shrunk, &[1, 2, 3]);
}
#[test]
fn test_realloc_zero_returns_original_usable() {
let buf = DataBuf::<[i32]>::alloc_from_copy(&[7, 8, 9]).unwrap();
let (err, orig) = buf.realloc(0).unwrap_err();
assert!(matches!(err, AllocationError::ZeroBytes));
// The original buffer must come back untouched and still owned.
assert_eq!(&*orig, &[7, 8, 9]);
}
#[test]
fn test_slice_drop_count() {
use std::sync::atomic::{AtomicUsize, Ordering};
struct DropCounter<'a>(&'a AtomicUsize);
impl Clone for DropCounter<'_> {
fn clone(&self) -> Self {
DropCounter(self.0)
}
}
impl Drop for DropCounter<'_> {
fn drop(&mut self) {
self.0.fetch_add(1, Ordering::SeqCst);
}
}
let drops = AtomicUsize::new(0);
{
let src = [
DropCounter(&drops),
DropCounter(&drops),
DropCounter(&drops),
];
let buf = DataBuf::<[DropCounter]>::alloc_from_clone(&src).unwrap();
assert_eq!(drops.load(Ordering::SeqCst), 0, "no drops while alive");
drop(buf);
assert_eq!(
drops.load(Ordering::SeqCst),
3,
"each buffer element dropped exactly once"
);
// `src` drops here → +3.
}
assert_eq!(drops.load(Ordering::SeqCst), 6);
}
#[test]
fn test_into_inner_suppresses_content_drop() {
use std::sync::atomic::{AtomicUsize, Ordering};
struct DropCounter<'a>(&'a AtomicUsize);
impl Drop for DropCounter<'_> {
fn drop(&mut self) {
self.0.fetch_add(1, Ordering::SeqCst);
}
}
let drops = AtomicUsize::new(0);
let buf = DataBuf::alloc_from(DropCounter(&drops))
.map_err(|(e, _)| e)
.unwrap();
let raw = buf.into_inner(); // DataBuf::drop suppressed
assert_eq!(drops.load(Ordering::SeqCst), 0, "into_inner must not drop");
// mem_free frees the raw bytes; it does NOT call Drop on the contained
// value — the DropCounter's destructor is intentionally never run here.
raw.mem_free(); // manual free — omitting this is what the LSAN leg would flag
assert_eq!(
drops.load(Ordering::SeqCst),
0,
"mem_free is documented not to drop contents"
);
}
#[test]
fn test_view_parity() {
let mut buf = DataBuf::<[u8]>::alloc_from_copy(b"hello").unwrap();
assert_eq!(&*buf, b"hello"); // Deref
assert_eq!(buf.as_ref(), b"hello"); // inherent as_ref
buf.as_mut()[0] = b'H'; // inherent as_mut
assert_eq!(&*buf, b"Hello");
let via_trait: &[u8] = AsRef::as_ref(&buf); // trait AsRef
assert_eq!(via_trait, b"Hello");
let via_trait_mut: &mut [u8] = AsMut::as_mut(&mut buf); // trait AsMut
via_trait_mut[1] = b'E';
assert_eq!(&*buf, b"HEllo");
}
#[test]
#[should_panic(expected = "`count` should be positive")]
fn test_slice_from_raw_zero_count_panics() {
// SAFETY: 4 is non-zero.
let ptr = unsafe { ffi::MemAlloc(4) }.cast::<i32>();
assert!(!ptr.is_null(), "should be able to allocate");
// count == 0 with a non-null ptr violates slice_from_raw's contract.
// (The allocation leaks on the panic path — fine in a should_panic test
// that is not part of the LSAN run-set.)
// SAFETY: ptr is non-null and Raylib-allocated; count is intentionally
// zero to exercise the panic guard.
let _ = unsafe { DataBuf::slice_from_raw(ptr, MaybeUninit::new(0)) };
}
}