diff --git a/examples/decorator/src/lib.rs b/examples/decorator/src/lib.rs index ca6db532..de3fe125 100644 --- a/examples/decorator/src/lib.rs +++ b/examples/decorator/src/lib.rs @@ -1,6 +1,6 @@ use pyo3::prelude::*; use pyo3::types::{PyDict, PyTuple}; -use std::sync::atomic::{AtomicU64, Ordering}; +use std::cell::Cell; /// A function decorator that keeps track how often it is called. /// @@ -9,8 +9,8 @@ use std::sync::atomic::{AtomicU64, Ordering}; pub struct PyCounter { // Keeps track of how many calls have gone through. // - // See the discussion at the end for why an atomic is used. - count: AtomicU64, + // See the discussion at the end for why `Cell` is used. + count: Cell, // This is the actual function being wrapped. wraps: Py, @@ -26,14 +26,14 @@ impl PyCounter { #[new] fn __new__(wraps: Py) -> Self { PyCounter { - count: AtomicU64::new(0), + count: Cell::new(0), wraps, } } #[getter] fn count(&self) -> u64 { - self.count.load(Ordering::Relaxed) + self.count.get() } #[args(args = "*", kwargs = "**")] @@ -43,10 +43,12 @@ impl PyCounter { args: &PyTuple, kwargs: Option<&PyDict>, ) -> PyResult> { - let old_count = self.count.fetch_add(1, Ordering::Relaxed); + let old_count = self.count.get(); + let new_count = old_count + 1; + self.count.set(new_count); let name = self.wraps.getattr(py, "__name__")?; - println!("{} has been called {} time(s).", name, old_count + 1); + println!("{} has been called {} time(s).", name, new_count); // After doing something, we finally forward the call to the wrapped function let ret = self.wraps.call(py, args, kwargs)?; diff --git a/guide/src/class/call.md b/guide/src/class/call.md index be544e70..9485d45f 100644 --- a/guide/src/class/call.md +++ b/guide/src/class/call.md @@ -66,7 +66,7 @@ def Counter(wraps): return call ``` -### What are the atomics for? +### What is the `Cell` for? A [previous implementation] used a normal `u64`, which meant it required a `&mut self` receiver to update the count: @@ -103,7 +103,7 @@ say_hello() # RuntimeError: Already borrowed ``` -The implementation in this chapter fixes that by never borrowing exclusively; all the methods take `&self` as receivers, of which multiple may exist simultaneously. This requires a shared and threadsafe counter and the easiest way to do that is to use atomics, so that's what is used here. +The implementation in this chapter fixes that by never borrowing exclusively; all the methods take `&self` as receivers, of which multiple may exist simultaneously. This requires a shared counter and the easiest way to do that is to use [`Cell`], so that's what is used here. This shows the dangers of running arbitrary Python code - note that "running arbitrary Python code" can be far more subtle than the example above: - Python's asynchronous executor may park the current thread in the middle of Python code, even in Python code that *you* control, and let other Python code run. @@ -112,4 +112,5 @@ This shows the dangers of running arbitrary Python code - note that "running arb This especially important if you are writing unsafe code; Python code must never be able to cause undefined behavior. You must ensure that your Rust code is in a consistent state before doing any of the above things. -[previous implementation]: https://github.com/PyO3/pyo3/discussions/2598 "Thread Safe Decorator · Discussion #2598 · PyO3/pyo3" \ No newline at end of file +[previous implementation]: https://github.com/PyO3/pyo3/discussions/2598 "Thread Safe Decorator · Discussion #2598 · PyO3/pyo3" +[`Cell`]: https://doc.rust-lang.org/std/cell/struct.Cell.html "Cell in std::cell - Rust" \ No newline at end of file