diff --git a/crates/roc_std/src/lib.rs b/crates/roc_std/src/lib.rs index 47108e9c41..232b224baf 100644 --- a/crates/roc_std/src/lib.rs +++ b/crates/roc_std/src/lib.rs @@ -19,9 +19,9 @@ mod storage; pub use roc_box::RocBox; pub use roc_dict::RocDict; -pub use roc_list::RocList; +pub use roc_list::{RocList, SendSafeRocList}; pub use roc_set::RocSet; -pub use roc_str::{InteriorNulError, RocStr}; +pub use roc_str::{InteriorNulError, RocStr, SendSafeRocStr}; pub use storage::Storage; // A list of C functions that are being imported diff --git a/crates/roc_std/src/roc_list.rs b/crates/roc_std/src/roc_list.rs index 050bdfe1bf..64e7280144 100644 --- a/crates/roc_std/src/roc_list.rs +++ b/crates/roc_std/src/roc_list.rs @@ -121,6 +121,15 @@ impl RocList { } } + // Marks a list as readonly. This means that it will be leaked. + // For constants passed in from platform to application, this may be reasonable. + // Marked unsafe because it should not be used lightly. + pub unsafe fn set_readonly(&self) { + if let Some((_, storage)) = self.elements_and_storage() { + storage.set(Storage::Readonly); + } + } + /// Note that there is no way to convert directly to a Vec. /// /// This is because RocList values are not allocated using the system allocator, so diff --git a/crates/roc_std/src/roc_str.rs b/crates/roc_std/src/roc_str.rs index e8ce58771a..837ccb948e 100644 --- a/crates/roc_std/src/roc_str.rs +++ b/crates/roc_std/src/roc_str.rs @@ -123,6 +123,16 @@ impl RocStr { } } + // Marks a str as readonly. This means that it will be leaked. + // For constants passed in from platform to application, this may be reasonable. + // Marked unsafe because it should not be used lightly. + pub unsafe fn set_readonly(&self) { + match self.as_enum_ref() { + RocStrInnerRef::HeapAllocated(roc_list) => unsafe { roc_list.set_readonly() }, + RocStrInnerRef::SmallString(_) => {} + } + } + /// Note that there is no way to convert directly to a String. /// /// This is because RocStr values are not allocated using the system allocator, so diff --git a/crates/roc_std/tests/test_roc_std.rs b/crates/roc_std/tests/test_roc_std.rs index d81b93d896..4de4e543fe 100644 --- a/crates/roc_std/tests/test_roc_std.rs +++ b/crates/roc_std/tests/test_roc_std.rs @@ -59,7 +59,7 @@ pub unsafe extern "C" fn roc_memset(dst: *mut c_void, c: i32, n: usize) -> *mut #[cfg(test)] mod test_roc_std { - use roc_std::{RocBox, RocDec, RocList, RocResult, RocStr}; + use roc_std::{RocBox, RocDec, RocList, RocResult, RocStr, SendSafeRocList, SendSafeRocStr}; fn roc_str_byte_representation(string: &RocStr) -> [u8; RocStr::SIZE] { unsafe { core::mem::transmute_copy(string) } @@ -126,7 +126,7 @@ mod test_roc_std { roc_str.reserve(42); - assert_gte!(roc_str.capacity(), 42); + assert_eq!(roc_str.capacity() >= 42, true); } #[test] @@ -135,7 +135,7 @@ mod test_roc_std { roc_str.reserve(5000); - assert_gte!(roc_str.capacity(), 5000); + assert_eq!(roc_str.capacity() >= 5000, true); } #[test] @@ -296,6 +296,80 @@ mod test_roc_std { let example = RocDec::from_str("1234.5678").unwrap(); assert_eq!(format!("{}", example), "1234.5678"); } + + #[test] + fn safe_send_no_copy() { + let x = RocStr::from("This is a long string but still unique. Yay!!!"); + assert_eq!(x.is_unique(), true); + + let safe_x = SendSafeRocStr::from(x); + let new_x = RocStr::from(safe_x); + assert_eq!(new_x.is_unique(), true); + assert_eq!( + new_x.as_str(), + "This is a long string but still unique. Yay!!!" + ); + } + + #[test] + fn safe_send_requires_copy() { + let x = RocStr::from("This is a long string but still unique. Yay!!!"); + let y = x.clone(); + let z = y.clone(); + assert_eq!(x.is_unique(), false); + assert_eq!(y.is_unique(), false); + assert_eq!(z.is_unique(), false); + + let safe_x = SendSafeRocStr::from(x); + let new_x = RocStr::from(safe_x); + assert_eq!(new_x.is_unique(), true); + assert_eq!(y.is_unique(), false); + assert_eq!(z.is_unique(), false); + assert_eq!( + new_x.as_str(), + "This is a long string but still unique. Yay!!!" + ); + } + + #[test] + fn safe_send_small_str() { + let x = RocStr::from("short"); + let y = x.clone(); + let z = y.clone(); + assert_eq!(x.is_unique(), true); + assert_eq!(y.is_unique(), true); + assert_eq!(z.is_unique(), true); + + let safe_x = SendSafeRocStr::from(x); + let new_x = RocStr::from(safe_x); + assert_eq!(new_x.is_unique(), true); + assert_eq!(y.is_unique(), true); + assert_eq!(z.is_unique(), true); + assert_eq!(new_x.as_str(), "short"); + } + + #[test] + fn empty_list_is_unique() { + let roc_list = RocList::::empty(); + assert_eq!(roc_list.is_unique(), true); + } + + #[test] + fn readonly_list_is_sendsafe() { + let x = RocList::from_slice(&[1, 2, 3, 4, 5]); + unsafe { x.set_readonly() }; + assert_eq!(x.is_readonly(), true); + + let y = x.clone(); + let z = y.clone(); + + let safe_x = SendSafeRocList::from(x); + let new_x = RocList::from(safe_x); + assert_eq!(new_x.is_readonly(), true); + assert_eq!(y.is_readonly(), true); + assert_eq!(z.is_readonly(), true); + assert_eq!(new_x.as_slice(), &[1, 2, 3, 4, 5]); + } } #[cfg(test)]