I'm trying to generate a random number in a Near smart contract using Rust. However when I run this code:
pub fn get_last(&self) -> u64 {
let mut rng = rand::thread_rng();
println!("i32: {}, i32: {}", rng.gen::<i32>(), rng.gen::<i32>());
return self.lastval;
}
I've got this error message:
$ near view $ID get_last '{}'
View call: dev-1643558356736-93385541578458.get_last({})
An error occured
Error: Querying [object Object] failed: wasm execution failed with error: FunctionCallError(HostError(GuestPanic { panic_msg: "panicked at 'internal error: entered unreachable code', C:\\Users\\GANSOR-PC\\.cargo\\registry\\src\\github.com-1ecc6299db9ec823\\rand-0.4.6\\src\\jitter.rs:703:9" })).
----------------
jitter.rs:703:9
#[cfg(all(target_arch = "wasm32", not(target_os = "emscripten")))]
pub fn get_nstime() -> u64 {
unreachable!()
}
in lib.rs I have:
use rand::{thread_rng, Rng};
extern crate rand;
And in Cargo.toml I have:
[dependencies]
near-sdk = "3.1.0"
near-contract-standards = "3.1.1"
rand = "*"
So, where am I wrong?
you can't use a typical random number generator since you are running inside of a virtual machine with no access to typical random seed generators like hardware clock or other machine data
instead consider using the provided random seed you can get through a couple of functions exposed on env like env::random_seed https://github.com/near/near-sdk-rs/blob/master/near-sdk/src/environment/env.rs#L236-L254
/// Returns the random seed from the current block. This 32 byte hash is based on the VRF value from
/// the block. This value is not modified in any way each time this function is called within the
/// same method/block.
pub fn random_seed() -> Vec<u8> {
random_seed_array().to_vec()
}
/// Returns the random seed from the current block. This 32 byte hash is based on the VRF value from
/// the block. This value is not modified in any way each time this function is called within the
/// same method/block.
pub fn random_seed_array() -> [u8; 32] {
//* SAFETY: random_seed syscall will always generate 32 bytes inside of the atomic op register
//* so the read will have a sufficient buffer of 32, and can transmute from uninit
//* because all bytes are filled. This assumes a valid random_seed implementation.
unsafe {
sys::random_seed(ATOMIC_OP_REGISTER);
read_register_fixed_32(ATOMIC_OP_REGISTER)
}
}
here's an example
https://github.com/near-apps/coin-flip/blob/1476bbdf0fee3b6647766ee6e94e40254f728191/contracts/src/lib.rs#L50-L59
pub fn play(&mut self) -> u8 {
let account_id = env::signer_account_id();
let mut credits = self.credits.get(&account_id).unwrap_or(0);
assert!(credits > 0, "no credits to play");
credits = credits - ONE_NEAR;
let rand: u8 = *env::random_seed().get(0).unwrap();
if rand < PROB {
credits = credits + 10 * ONE_NEAR;
}
self.credits.insert(&account_id, &credits);
rand
}
Related
I am working with Rust on a Teensy 4.0 ( --target thumbv7em-none-eabihf ) which means I have to use #![no_std] .
I have some situations where I want to do different things depending on the position of a rotary switch.
The following is a toy example that illustrates a problem where I want to return one of a series of objects that implement a trait.
fn text_for(selector: i32) -> impl Fn()->Box<dyn Iterator<Item=char>> {
match selector {
1 => || {
let rval : Box<dyn Iterator<Item=char>> = Box::new("author".chars());
rval
},
_ => || {
let rval : Box::<dyn Iterator<Item=char>> = Box::new(b"baseline".iter().map(|&b| (b) as char));
rval
}
}
}
Unfortunately Box is not available in the no_std environment. I have seen references to an alloc crate (Is it possible to use Box with no_std?), but when I use extern crate alloc; use alloc::boxed::Box; the compiler complains
error: no global memory allocator found but one is required; link to std or add `#[global_allocator]` to a static item that implements the GlobalAlloc trait.
error: `#[alloc_error_handler]` function required, but not found.
note: Use `#![feature(default_alloc_error_handler)]` for a default error handler.
Attempting to use the alloc-cortex-m crate to use a CortexMHeap as the #[global_allocator] as suggested by lkolbly results in the following error
error[E0554]: `#![feature]` may not be used on the stable release channel
--> /home/thoth/.cargo/registry/src/github.com-1ecc6299db9ec823/linked_list_allocator-0.8.11/src/lib.rs:1:41
|
1 | #![cfg_attr(feature = "const_mut_refs", feature(const_mut_refs))]
| ^^^^^^^^^^^^^^^^^^^^^^^
How can I work with dyn instances of a trait in a no_std environment using stable rust?
Here's an example of a global allocator, adapted from alloc-cortex-m without using the feature const_mut_refs of linked_list_allocator that requires nightly Rust.
Note: The code was not tested.
Cargo.toml:
[dependencies]
linked_list_allocator = { version = "0.9", default-features = false, features = ["use_spin"] }
lib.rs:
#![no_std]
mod allocator;
extern crate alloc;
use alloc::boxed::Box;
#[global_allocator]
static ALLOCATOR: allocator::CortexMHeap = unsafe { allocator::CortexMHeap::empty() };
#[entry]
fn main() -> ! {
// Initialize the allocator BEFORE you use it
let start = cortex_m_rt::heap_start() as usize;
let size = 1024; // in bytes
unsafe { ALLOCATOR.init(start, size) }
// ...
}
// Your code using `Box` here.
allocator.rs:
use core::alloc::{GlobalAlloc, Layout};
use core::cell::RefCell;
use core::ptr::{self, NonNull};
use core::mem::MaybeUninit;
use cortex_m::interrupt::Mutex;
use linked_list_allocator::Heap;
pub struct CortexMHeap {
heap: Mutex<RefCell<MaybeUninit<Heap>>>,
}
impl CortexMHeap {
/// Crate a new UNINITIALIZED heap allocator
///
/// # Safety
///
/// You must initialize this heap using the
/// [`init`](struct.CortexMHeap.html#method.init) method before using the allocator.
pub const unsafe fn empty() -> CortexMHeap {
CortexMHeap {
heap: Mutex::new(RefCell::new(MaybeUninit::uninit())),
}
}
fn heap(&self, cs: &cortex_m::interrupt::CriticalSection) -> &mut Heap {
let heap = &mut *self.heap.borrow(cs).borrow_mut();
// SAFETY: `init()` initializes this, and it's guaranteed to be called by preconditions of `empty()`.
unsafe { &mut *heap.as_mut_ptr() }
}
/// Initializes the heap
///
/// This function must be called BEFORE you run any code that makes use of the
/// allocator.
///
/// `start_addr` is the address where the heap will be located.
///
/// `size` is the size of the heap in bytes.
///
/// Note that:
///
/// - The heap grows "upwards", towards larger addresses. Thus `end_addr` must
/// be larger than `start_addr`
///
/// - The size of the heap is `(end_addr as usize) - (start_addr as usize)`. The
/// allocator won't use the byte at `end_addr`.
///
/// # Safety
///
/// Obey these or Bad Stuff will happen.
///
/// - This function must be called exactly ONCE.
/// - `size > 0`
pub unsafe fn init(&self, start_addr: usize, size: usize) {
cortex_m::interrupt::free(|cs| {
let heap = &mut *self.heap.borrow(cs).borrow_mut();
*heap = MaybeUninit::new(Heap::empty());
(*heap.as_mut_ptr()).init(start_addr, size);
});
}
/// Returns an estimate of the amount of bytes in use.
pub fn used(&self) -> usize {
cortex_m::interrupt::free(|cs| self.heap(cs).used())
}
/// Returns an estimate of the amount of bytes available.
pub fn free(&self) -> usize {
cortex_m::interrupt::free(|cs| self.heap(cs).free())
}
}
unsafe impl GlobalAlloc for CortexMHeap {
unsafe fn alloc(&self, layout: Layout) -> *mut u8 {
cortex_m::interrupt::free(|cs| {
self.heap(cs)
.allocate_first_fit(layout)
.ok()
.map_or(ptr::null_mut(), |allocation| allocation.as_ptr())
})
}
unsafe fn dealloc(&self, ptr: *mut u8, layout: Layout) {
cortex_m::interrupt::free(|cs| {
self.heap(cs)
.deallocate(NonNull::new_unchecked(ptr), layout)
});
}
}
One option, shown in the other answer, is to allow the use of boxes by providing an allocator. But if you don't require allocations anywhere else in your code, it's not necessary to introduce them just for the sake of trait objects - you can also create trait objects by borrowing stack-allocated values.
That will require changing in the API so that the closure doesn't return the iterator, but passes it to a user-provided closure that consumes it. The consuming closure can iterate over it and extract useful data. For example:
fn text_for<F, R>(selector: i32) -> impl Fn(F) -> R
where
F: Fn(&mut dyn Iterator<Item = char>) -> R,
{
move |consume| {
let (mut it1, mut it2);
let it: &mut dyn Iterator<Item = char> = match selector {
1 => {
it1 = "author".chars();
&mut it1
}
_ => {
it2 = b"baseline".iter().map(|&b| b as char);
&mut it2
}
};
consume(it)
}
}
You could use this version of text_for() like this:
fn main() {
let with_text_iter = text_for(1);
assert_eq!(
// example consume function just collects chars into Vec
with_text_iter(|it| it.collect::<Vec<_>>()),
&['a', 'u', 't', 'h', 'o', 'r']
);
}
Playground
I just working with the "Quick-start Guide" in the last part "invoking methods" when i try to call any method always see the error:
Failure [xxxx.testnet]
Error: Contract method is not found
An error occured
Error: Contract method is not found
I have all the methods becase is only copy paste tutorial.
** UPDATE **
I just follow the steps.
Deploy is correct:
near deploy --wasmFile target/wasm32-unknown-unknown/release/rust_counter_tutorial.wasm --accountId josedlujan.testnet
The msg:
Starting deployment. Account id: josedlujan.testnet, node: https://rpc.testnet.near.org, helper: https://helper.testnet.near.org, file: target/wasm32-unknown-unknown/release/rust_counter_tutorial.wasm
Transaction Id 5LiJSuaso4XJ85gYcMopgECgAMvK4BxAHGgfTuGxYHAY
To see the transaction in the transaction explorer, please open this url in your browser
https://explorer.testnet.near.org/transactions/5LiJSuaso4XJ85gYcMopgECgAMvK4BxAHGgfTuGxYHAY
But, when i execute methods.
near call josedlujan.testnet increment --accountId josedlujan.testnet
I always see:
Scheduling a call: josedlujan.testnet.increment()
Receipt: 7GsyvaVGErEr7rXkQdBQSu5F3FRFCQ2BG2LJuDT3t4Gx
Failure [josedlujan.testnet]: Error: Contract method is not found
An error occured
Error: Contract method is not found
But i have the methods lib.rs:
pub fn increment(&mut self) {
// note: adding one like this is an easy way to accidentally overflow
// real smart contracts will want to have safety checks
// e.g. self.val = i8::wrapping_add(self.val, 1);
// https://doc.rust-lang.org/std/primitive.i8.html#method.wrapping_add
self.val += 1;
let log_message = format!("Increased number to {}", self.val);
env::log(log_message.as_bytes());
after_counter_change();
}
/// Decrement (subtract from) the counter.
///
/// In (/src/main.js) this is also added to the "changeMethods" array
/// using near-cli we can call this by:
///
/// ```bash
/// near call counter.YOU.testnet decrement --accountId donation.YOU.testnet
/// ```
pub fn decrement(&mut self) {
// note: subtracting one like this is an easy way to accidentally overflow
// real smart contracts will want to have safety checks
// e.g. self.val = i8::wrapping_sub(self.val, 1);
// https://doc.rust-lang.org/std/primitive.i8.html#method.wrapping_sub
self.val -= 1;
let log_message = format!("Decreased number to {}", self.val);
env::log(log_message.as_bytes());
after_counter_change();
}
lib.rs
//! This contract implements simple counter backed by storage on blockchain.
//!
//! The contract provides methods to [increment] / [decrement] counter and
//! [get it's current value][get_num] or [reset].
//!
//! [increment]: struct.Counter.html#method.increment
//! [decrement]: struct.Counter.html#method.decrement
//! [get_num]: struct.Counter.html#method.get_num
//! [reset]: struct.Counter.html#method.reset
use near_sdk::borsh::{self, BorshDeserialize, BorshSerialize};
use near_sdk::{env, near_bindgen};
near_sdk::setup_alloc!();
// add the following attributes to prepare your code for serialization and invocation on the blockchain
// More built-in Rust attributes here: https://doc.rust-lang.org/reference/attributes.html#built-in-attributes-index
#[near_bindgen]
#[derive(Default, BorshDeserialize, BorshSerialize)]
pub struct Counter {
// See more data types at https://doc.rust-lang.org/book/ch03-02-data-types.html
val: i8, // i8 is signed. unsigned integers are also available: u8, u16, u32, u64, u128
}
#[near_bindgen]
impl Counter {
/// Returns 8-bit signed integer of the counter value.
///
/// This must match the type from our struct's 'val' defined above.
///
/// Note, the parameter is `&self` (without being mutable) meaning it doesn't modify state.
/// In the frontend (/src/main.js) this is added to the "viewMethods" array
/// using near-cli we can call this by:
///
/// ```bash
/// near view counter.YOU.testnet get_num
/// ```
pub fn get_num(&self) -> i8 {
return self.val;
}
/// Increment the counter.
///
/// Note, the parameter is "&mut self" as this function modifies state.
/// In the frontend (/src/main.js) this is added to the "changeMethods" array
/// using near-cli we can call this by:
///
/// ```bash
/// near call counter.YOU.testnet increment --accountId donation.YOU.testnet
/// ```
pub fn increment(&mut self) {
// note: adding one like this is an easy way to accidentally overflow
// real smart contracts will want to have safety checks
// e.g. self.val = i8::wrapping_add(self.val, 1);
// https://doc.rust-lang.org/std/primitive.i8.html#method.wrapping_add
self.val += 1;
let log_message = format!("Increased number to {}", self.val);
env::log(log_message.as_bytes());
after_counter_change();
}
/// Decrement (subtract from) the counter.
///
/// In (/src/main.js) this is also added to the "changeMethods" array
/// using near-cli we can call this by:
///
/// ```bash
/// near call counter.YOU.testnet decrement --accountId donation.YOU.testnet
/// ```
pub fn decrement(&mut self) {
// note: subtracting one like this is an easy way to accidentally overflow
// real smart contracts will want to have safety checks
// e.g. self.val = i8::wrapping_sub(self.val, 1);
// https://doc.rust-lang.org/std/primitive.i8.html#method.wrapping_sub
self.val -= 1;
let log_message = format!("Decreased number to {}", self.val);
env::log(log_message.as_bytes());
after_counter_change();
}
/// Reset to zero.
pub fn reset(&mut self) {
self.val = 0;
// Another way to log is to cast a string into bytes, hence "b" below:
env::log(b"Reset counter to zero");
}
}
// unlike the struct's functions above, this function cannot use attributes #[derive(…)] or #[near_bindgen]
// any attempts will throw helpful warnings upon 'cargo build'
// while this function cannot be invoked directly on the blockchain, it can be called from an invoked function
fn after_counter_change() {
// show helpful warning that i8 (8-bit signed integer) will overflow above 127 or below -128
env::log("Make sure you don't overflow, my friend.".as_bytes());
}
/*
* the rest of this file sets up unit tests
* to run these, the command will be:
* cargo test --package rust-counter-tutorial -- --nocapture
* Note: 'rust-counter-tutorial' comes from cargo.toml's 'name' key
*/
// use the attribute below for unit tests
#[cfg(test)]
mod tests {
use super::*;
use near_sdk::MockedBlockchain;
use near_sdk::{testing_env, VMContext};
// part of writing unit tests is setting up a mock context
// in this example, this is only needed for env::log in the contract
// this is also a useful list to peek at when wondering what's available in env::*
fn get_context(input: Vec<u8>, is_view: bool) -> VMContext {
VMContext {
current_account_id: "alice.testnet".to_string(),
signer_account_id: "robert.testnet".to_string(),
signer_account_pk: vec![0, 1, 2],
predecessor_account_id: "jane.testnet".to_string(),
input,
block_index: 0,
block_timestamp: 0,
account_balance: 0,
account_locked_balance: 0,
storage_usage: 0,
attached_deposit: 0,
prepaid_gas: 10u64.pow(18),
random_seed: vec![0, 1, 2],
is_view,
output_data_receivers: vec![],
epoch_height: 19,
}
}
// mark individual unit tests with #[test] for them to be registered and fired
#[test]
fn increment() {
// set up the mock context into the testing environment
let context = get_context(vec![], false);
testing_env!(context);
// instantiate a contract variable with the counter at zero
let mut contract = Counter { val: 0 };
contract.increment();
println!("Value after increment: {}", contract.get_num());
// confirm that we received 1 when calling get_num
assert_eq!(1, contract.get_num());
}
#[test]
fn decrement() {
let context = get_context(vec![], false);
testing_env!(context);
let mut contract = Counter { val: 0 };
contract.decrement();
println!("Value after decrement: {}", contract.get_num());
// confirm that we received -1 when calling get_num
assert_eq!(-1, contract.get_num());
}
#[test]
fn increment_and_reset() {
let context = get_context(vec![], false);
testing_env!(context);
let mut contract = Counter { val: 0 };
contract.increment();
contract.reset();
println!("Value after reset: {}", contract.get_num());
// confirm that we received -1 when calling get_num
assert_eq!(0, contract.get_num());
}
}
```
I assume you are referring to the Quick-start guide for building smart contracts in Rust?
Let's check a few things to make sure you didn't miss any steps.
Did you create a lib.rs file and copy the contents of this smart contract into that file?
Did you successfully deploy the contract using near-cli after you compiled it to WASM?
Are you invoking the methods using near-cli and replacing YOUR_ACCOUNT_HERE with the testnet accountId you just deployed the contract to?
If so, would you be able to share your code / commands so I can help you debug your issue? This tutorial was updated recently (less than a month from today) so it should work, but while I'm waiting for your response I will run through the steps to make sure we don't have a bug on our end. :)
** UPDATE **
Just went through the tutorial and it worked for me. Here was my command using near-cli:
near call ex-1.testnet increment --accountId ex-1.testnet
And the response:
Scheduling a call: ex-1.testnet.increment()
Receipt: 3UTWH43Tgxy1kSSvoax5cRu8A2VEDKZqzgZaEXTwzsBD
Log [ex-1.testnet]: Increased number to 1
Log [ex-1.testnet]: Make sure you don't overflow, my friend.
Transaction Id 2U92KghmCqihUuURVaLGj8x8FrBhQ5z1vE35BVdZG1ob
To see the transaction in the transaction explorer, please open this url in your browser
https://explorer.testnet.near.org/transactions/2U92KghmCqihUuURVaLGj8x8FrBhQ5z1vE35BVdZG1ob
Feel free to reach out on our Discord for further assistance. The proper channel for this discussion would be #near-sdk-rs under #development but #dev-support is ok too. :)
For the library I need to write I have a struct representing a service written in C:
struct RustService {
id: u8, /* it's me setting the id in the first place */
service: *mut c_void,
status: *mut c_void,
value: Option<u32>,
/* various fields here */
}
impl RustService {
fn new() -> /* can be wrapped*/RustService {
/* ... */
}
}
I have several callbacks passed to C library like this:
#[no_mangle]
extern "C" fn callback_handle(service: *mut c_void, size: *mut u32, buffer: *mut u8)
{
let id: u8 = c_library_get_id(service);
let instance: &mut RustService = /* get that from static container based on id */
if size = mem::size_of::<u32>() as u32 {
let value = buffer.cast::<u32>().read();
instance.value = Some(value);
} else {
panic!("Invalid length passed.");
}
}
What am I looking for is a proven/good practice that would apply here that works without causing deadlock, where my callback could refer back to the right instance using the library in C. That library in C can run multiple instances, however it's not that important. Alternatively I can accept a single instance pattern, that would work in this case.
Other methods of RustService would need to call for example a function with the following signature (from bindgen):
extern "C" pub fn clibCoreWork(status: *mut clibStatus, service: *mut clibService) -> u32;
and guts of that function may decide to call these callbacks mentioned above.
The approach I've taken shoots me in the foot as the inner Mutex deadlocks even in the single thread:
lazy_static! {
static ref MY_INSTANCES: Mutex<HashMap<u8, Weak<Mutex<RustService>>>> = Mutex::new(HashMap::new());
}
I dug the internet but fellow Rustaceans didn't seem to post anything about such problem.
Note: I cannot change underlying types in the C library I'm using. In the ideal world it would let me store pointer to user data and I'd brutally convert it to a mutable reference in unsafe code.
This question already has an answer here:
How can I call a raw address from Rust?
(1 answer)
Closed 3 years ago.
Hello people of the internet,
I'm struggeling to invoke a function that is stored in a libc::c_void-Pointer. I can't tell Rust that the pointer is invokable and I can't figure out how to.
I want to translate this C++ Code
void * malloc(size_t size) {
static void *(*real_malloc)(size_t) = nullptr;
if (real_malloc == nullptr) {
real_malloc = reinterpret_cast<void *(*)(size_t)> (dlsym(RTLD_NEXT, "malloc"));
}
// do some logging stuff
void * ptr = real_malloc(size);
return ptr;
}
to Rust.
#[no_mangle]
pub extern fn malloc(bytes: usize) {
let c_string = "malloc\0".as_mut_ptr() as *mut i8; // char array for libc
let real_malloc: *mut libc::c_void = libc::dlsym(libc::RTLD_NEXT, c_string);
return real_malloc(bytes);
}
That's my progress so far after 1h of searching on the internet and trying. I'm new to Rust and not yet familiar with Rust/FFI / Rust with libc. I tried a lot with unsafe{}, casts with as but I always stuck at the following problem:
return real_malloc(bytes);
^^^^^^^^^^^^^^^^^^ expected (), found *-ptr
Q1: How can I call the function behind the void-Pointer stored in real_malloc?
Q2: Is my Rust-String to C-String conversion feasible this way?
I figured it out! Perhaps there is a better way but it works.
The trick is to "cast" the void-Pointer to c-function-Type with std::mem::transmute since it won't work with as
type LibCMallocT = fn(usize) -> *mut libc::c_void;
// C-Style string for symbol-name
let c_string = "malloc\0".as_ptr() as *mut i8; // char array for libc
// Void-Pointer to address of symbol
let real_malloc_addr: *mut libc::c_void = unsafe {libc::dlsym(libc::RTLD_NEXT, c_string)};
// transmute: "Reinterprets the bits of a value of one type as another type"
// Transform void-pointer-type to callable C-Function
let real_malloc: LibCMallocT = unsafe { std::mem::transmute(real_malloc_addr) }
When the shared object is build, one can verify that it works like this:
LD_PRELOAD=./target/debug/libmalloc_log_lib.so some-binary
My full code:
extern crate libc;
use std::io::Write;
const MSG: &str = "HELLO WORLD\n";
type LibCMallocT = fn(usize) -> *mut libc::c_void;
#[no_mangle] // then "malloc" is the symbol name so that ELF-Files can find it (if this lib is preloaded)
pub extern fn malloc(bytes: usize) -> *mut libc::c_void {
/// Disable logging aka return immediately the pointer from the real malloc (libc malloc)
static mut RETURN_IMMEDIATELY: bool = false;
// C-Style string for symbol-name
let c_string = "malloc\0".as_ptr() as *mut i8; // char array for libc
// Void-Pointer to address of symbol
let real_malloc_addr: *mut libc::c_void = unsafe {libc::dlsym(libc::RTLD_NEXT, c_string)};
// transmute: "Reinterprets the bits of a value of one type as another type"
// Transform void-pointer-type to callable C-Function
let real_malloc: LibCMallocT = unsafe { std::mem::transmute(real_malloc_addr) };
unsafe {
if !RETURN_IMMEDIATELY {
// let's do logging and other stuff that potentially
// needs malloc() itself
// This Variable prevent infinite loops because 'std::io::stdout().write_all'
// also uses malloc itself
// TODO: Do proper synchronisazion
// (lock whole method? thread_local variable?)
RETURN_IMMEDIATELY = true;
match std::io::stdout().write_all(MSG.as_bytes()) {
_ => ()
};
RETURN_IMMEDIATELY = false
}
}
(real_malloc)(bytes)
}
PS: Thanks to https://stackoverflow.com/a/46134764/2891595 (After I googled a lot more I found the trick with transmute!)
This is something of a controversial topic, so let me start by explaining my use case, and then talk about the actual problem.
I find that for a bunch of unsafe things, it's important to make sure that you don't leak memory; this is actually quite easy to do if you start using transmute() and forget(). For example, passing a boxed instance to C code for an arbitrary amount of time, then fetching it back out and 'resurrecting it' by using transmute.
Imagine I have a safe wrapper for this sort of API:
trait Foo {}
struct CBox;
impl CBox {
/// Stores value in a bound C api, forget(value)
fn set<T: Foo>(value: T) {
// ...
}
/// Periodically call this and maybe get a callback invoked
fn poll(_: Box<Fn<(EventType, Foo), ()> + Send>) {
// ...
}
}
impl Drop for CBox {
fn drop(&mut self) {
// Safely load all saved Foo's here and discard them, preventing memory leaks
}
}
To test this is actually not leaking any memory, I want some tests like this:
#[cfg(test)]
mod test {
struct IsFoo;
impl Foo for IsFoo {}
impl Drop for IsFoo {
fn drop(&mut self) {
Static::touch();
}
}
#[test]
fn test_drops_actually_work() {
guard = Static::lock(); // Prevent any other use of Static concurrently
Static::reset(); // Set to zero
{
let c = CBox;
c.set(IsFoo);
c.set(IsFoo);
c.poll(/*...*/);
}
assert!(Static::get() == 2); // Assert that all expected drops were invoked
guard.release();
}
}
How can you create this type of static singleton object?
It must use a Semaphore style guard lock to ensure that multiple tests do not concurrently run, and then unsafely access some kind of static mutable value.
I thought perhaps this implementation would work, but practically speaking it fails because occasionally race conditions result in a duplicate execution of init:
/// Global instance
static mut INSTANCE_LOCK: bool = false;
static mut INSTANCE: *mut StaticUtils = 0 as *mut StaticUtils;
static mut WRITE_LOCK: *mut Semaphore = 0 as *mut Semaphore;
static mut LOCK: *mut Semaphore = 0 as *mut Semaphore;
/// Generate instances if they don't exist
unsafe fn init() {
if !INSTANCE_LOCK {
INSTANCE_LOCK = true;
INSTANCE = transmute(box StaticUtils::new());
WRITE_LOCK = transmute(box Semaphore::new(1));
LOCK = transmute(box Semaphore::new(1));
}
}
Note specifically that unlike a normal program where you can be certain that your entry point (main) is always running in a single task, the test runner in Rust does not offer any kind of single entry point like this.
Other, obviously, than specifying the maximum number of tasks; given dozens of tests, only a handful need to do this sort of thing, and it's slow and pointless to limit the test task pool to one just for this one case.
It looks like a use case for std::sync::Once:
use std::sync::{Once, ONCE_INIT};
static INIT: Once = ONCE_INIT;
Then in your tests call
INIT.doit(|| unsafe { init(); });
Once guarantees that your init will only be executed once, no matter how many times you call INIT.doit().
See also lazy_static, which makes things a little more ergonomic. It does essentially the same thing as a static Once for each variable, but wraps it in a type that implements Deref so that you can access it like a normal reference.
Usage looks like this (from the documentation):
#[macro_use]
extern crate lazy_static;
use std::collections::HashMap;
lazy_static! {
static ref HASHMAP: HashMap<u32, &'static str> = {
let mut m = HashMap::new();
m.insert(0, "foo");
m.insert(1, "bar");
m.insert(2, "baz");
m
};
static ref COUNT: usize = HASHMAP.len();
static ref NUMBER: u32 = times_two(21);
}
fn times_two(n: u32) -> u32 { n * 2 }
fn main() {
println!("The map has {} entries.", *COUNT);
println!("The entry for `0` is \"{}\".", HASHMAP.get(&0).unwrap());
println!("A expensive calculation on a static results in: {}.", *NUMBER);
}
Note that autoderef means that you don't even have to use * whenever you call a method on your static variable. The variable will be initialized the first time it's Deref'd.
However, lazy_static variables are immutable (since they're behind a reference). If you want a mutable static, you'll need to use a Mutex:
lazy_static! {
static ref VALUE: Mutex<u64>;
}
impl Drop for IsFoo {
fn drop(&mut self) {
let mut value = VALUE.lock().unwrap();
*value += 1;
}
}
#[test]
fn test_drops_actually_work() {
// Have to drop the mutex guard to unlock, so we put it in its own scope
{
*VALUE.lock().unwrap() = 0;
}
{
let c = CBox;
c.set(IsFoo);
c.set(IsFoo);
c.poll(/*...*/);
}
assert!(*VALUE.lock().unwrap() == 2); // Assert that all expected drops were invoked
}
If you're willing to use nightly Rust you can use SyncLazy instead of the external lazy_static crate:
#![feature(once_cell)]
use std::collections::HashMap;
use std::lazy::SyncLazy;
static HASHMAP: SyncLazy<HashMap<i32, String>> = SyncLazy::new(|| {
println!("initializing");
let mut m = HashMap::new();
m.insert(13, "Spica".to_string());
m.insert(74, "Hoyten".to_string());
m
});
fn main() {
println!("ready");
std::thread::spawn(|| {
println!("{:?}", HASHMAP.get(&13));
}).join().unwrap();
println!("{:?}", HASHMAP.get(&74));
// Prints:
// ready
// initializing
// Some("Spica")
// Some("Hoyten")
}