From 0d20f6aca89a308652de60e0137edc3056afea5f Mon Sep 17 00:00:00 2001 From: Greg Johnston Date: Tue, 16 Apr 2024 20:25:34 -0400 Subject: [PATCH] chore: publish `Oco` separately as `oco_ref` crate so that it can be used elsewhere (#2536) --- Cargo.toml | 4 ++ leptos_reactive/Cargo.toml | 1 + leptos_reactive/src/lib.rs | 2 +- leptos_reactive/src/signal_wrappers_read.rs | 35 ++++++++-- oco/Cargo.toml | 16 +++++ oco/README.md | 31 +++++++++ leptos_reactive/src/oco.rs => oco/src/lib.rs | 71 +++++++++++++++----- 7 files changed, 136 insertions(+), 24 deletions(-) create mode 100644 oco/Cargo.toml create mode 100644 oco/README.md rename leptos_reactive/src/oco.rs => oco/src/lib.rs (89%) diff --git a/Cargo.toml b/Cargo.toml index 8e7ed09c4..08aba7b18 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,9 @@ [workspace] resolver = "2" members = [ + # utilities + "oco", + # core "leptos", "leptos_dom", @@ -29,6 +32,7 @@ version = "0.6.11" rust-version = "1.75" [workspace.dependencies] +oco_ref = { path = "./oco", version = "0.1.0" } leptos = { path = "./leptos", version = "0.6.11" } leptos_dom = { path = "./leptos_dom", version = "0.6.11" } leptos_hot_reload = { path = "./leptos_hot_reload", version = "0.6.11" } diff --git a/leptos_reactive/Cargo.toml b/leptos_reactive/Cargo.toml index 35b7311e1..8ce136823 100644 --- a/leptos_reactive/Cargo.toml +++ b/leptos_reactive/Cargo.toml @@ -9,6 +9,7 @@ description = "Reactive system for the Leptos web framework." rust-version.workspace = true [dependencies] +oco_ref = { workspace = true } slotmap = { version = "1", features = ["serde"] } serde = { version = "1", features = ["derive"] } serde-lite = { version = "0.5", optional = true } diff --git a/leptos_reactive/src/lib.rs b/leptos_reactive/src/lib.rs index a2f371804..ed90662c2 100644 --- a/leptos_reactive/src/lib.rs +++ b/leptos_reactive/src/lib.rs @@ -95,7 +95,6 @@ mod hydration; pub mod macros; mod memo; mod node; -pub mod oco; mod resource; mod runtime; mod selector; @@ -120,6 +119,7 @@ pub use hydration::{FragmentData, SharedContext}; pub use memo::*; pub use node::Disposer; pub use oco::*; +pub use oco_ref as oco; pub use resource::*; use runtime::*; pub use runtime::{ diff --git a/leptos_reactive/src/signal_wrappers_read.rs b/leptos_reactive/src/signal_wrappers_read.rs index d703a6ee9..a1251fb8a 100644 --- a/leptos_reactive/src/signal_wrappers_read.rs +++ b/leptos_reactive/src/signal_wrappers_read.rs @@ -3,7 +3,7 @@ use crate::{ Oco, ReadSignal, RwSignal, SignalDispose, SignalGet, SignalGetUntracked, SignalStream, SignalWith, SignalWithUntracked, StoredValue, }; -use std::{fmt::Debug, rc::Rc}; +use std::{borrow::Cow, fmt::Debug, rc::Rc}; /// Helper trait for converting `Fn() -> T` closures into /// [`Signal`]. @@ -1345,12 +1345,33 @@ impl From> for TextProp { } } -impl From for MaybeProp -where - T: Into>, -{ - fn from(s: T) -> Self { - Self(Some(MaybeSignal::from(Some(s.into().into())))) +impl From for MaybeProp { + fn from(s: String) -> Self { + Self(Some(MaybeSignal::from(Some(Oco::from(s).into())))) + } +} + +impl From> for MaybeProp { + fn from(s: Rc) -> Self { + Self(Some(MaybeSignal::from(Some(Oco::from(s).into())))) + } +} + +impl From<&'static str> for MaybeProp { + fn from(s: &'static str) -> Self { + Self(Some(MaybeSignal::from(Some(Oco::from(s).into())))) + } +} + +impl From> for MaybeProp { + fn from(s: Box) -> Self { + Self(Some(MaybeSignal::from(Some(Oco::from(s).into())))) + } +} + +impl From> for MaybeProp { + fn from(s: Cow<'static, str>) -> Self { + Self(Some(MaybeSignal::from(Some(Oco::from(s).into())))) } } diff --git a/oco/Cargo.toml b/oco/Cargo.toml new file mode 100644 index 000000000..09cb364c9 --- /dev/null +++ b/oco/Cargo.toml @@ -0,0 +1,16 @@ +[package] +name = "oco_ref" +edition = "2021" +version = "0.1.1" +authors = ["Danik Vitek", "Greg Johnston"] +license = "MIT" +repository = "https://github.com/leptos-rs/leptos" +description = "A smart pointer for storing immutable values with relatively-cheap cloning. (Like a `Cow` meets an `Rc`!)" +rust-version.workspace = true + +[dependencies] +serde = "1" +thiserror = "1" + +[dev-dependencies] +serde_json = "1" diff --git a/oco/README.md b/oco/README.md new file mode 100644 index 000000000..fff8da542 --- /dev/null +++ b/oco/README.md @@ -0,0 +1,31 @@ + This module contains the `Oco` (Owned Clones Once) smart pointer, + which is used to store immutable references to values. + This is useful for storing, for example, strings. + + Imagine this as an alternative to [`Cow`] with an additional, reference-counted + branch. + + ```rust + use oco_ref::Oco; + use std::rc::Rc; + + let static_str = "foo"; + let rc_str: Rc = "bar".into(); + let owned_str: String = "baz".into(); + + fn uses_oco(value: impl Into>) { + let mut value = value.into(); + + // ensures that the value is either a reference, or reference-counted + // O(n) at worst + let clone1 = value.clone_inplace(); + + // these subsequent clones are O(1) + let clone2 = value.clone(); + let clone3 = value.clone(); + } + + uses_oco(static_str); + uses_oco(rc_str); + uses_oco(owned_str); + ``` diff --git a/leptos_reactive/src/oco.rs b/oco/src/lib.rs similarity index 89% rename from leptos_reactive/src/oco.rs rename to oco/src/lib.rs index 4ae60dde2..9a69ae673 100644 --- a/leptos_reactive/src/oco.rs +++ b/oco/src/lib.rs @@ -1,6 +1,37 @@ //! This module contains the `Oco` (Owned Clones Once) smart pointer, //! which is used to store immutable references to values. //! This is useful for storing, for example, strings. +//! +//! Imagine this as an alternative to [`Cow`] with an additional, reference-counted +//! branch. +//! +//! ```rust +//! use oco_ref::Oco; +//! use std::rc::Rc; +//! +//! let static_str = "foo"; +//! let rc_str: Rc = "bar".into(); +//! let owned_str: String = "baz".into(); +//! +//! fn uses_oco(value: impl Into>) { +//! let mut value = value.into(); +//! +//! // ensures that the value is either a reference, or reference-counted +//! // O(n) at worst +//! let clone1 = value.clone_inplace(); +//! +//! // these subsequent clones are O(1) +//! let clone2 = value.clone(); +//! let clone3 = value.clone(); +//! } +//! +//! uses_oco(static_str); +//! uses_oco(rc_str); +//! uses_oco(owned_str); +//! ``` + +#![forbid(unsafe_code)] +#![deny(missing_docs)] use serde::{de::DeserializeOwned, Deserialize, Serialize}; use std::{ @@ -13,16 +44,24 @@ use std::{ rc::Rc, }; -/// "Owned Clones Once" - a smart pointer that can be either a reference, -/// an owned value, or a reference counted pointer. This is useful for +/// "Owned Clones Once": a smart pointer that can be either a reference, +/// an owned value, or a reference-counted pointer. This is useful for /// storing immutable values, such as strings, in a way that is cheap to /// clone and pass around. /// -/// The `Clone` implementation is amortized `O(1)`. Cloning the [`Oco::Borrowed`] +/// The cost of the `Clone` implementation depends on the branch. Cloning the [`Oco::Borrowed`] /// variant simply copies the references (`O(1)`). Cloning the [`Oco::Counted`] /// variant increments a reference count (`O(1)`). Cloning the [`Oco::Owned`] -/// variant upgrades it to [`Oco::Counted`], which requires an `O(n)` clone of the -/// data, but all subsequent clones will be `O(1)`. +/// variant requires an `O(n)` clone of the data. +/// +/// For an amortized `O(1)` clone, you can use [`Oco::clone_inplace()`]. Using this method, +/// [`Oco::Borrowed`] and [`Oco::Counted`] are still `O(1)`. [`Oco::Owned`] does a single `O(n)` +/// clone, but converts the object to the [`Oco::Counted`] branch, which means future clones will +/// be `O(1)`. +/// +/// In general, you'll either want to call `clone_inplace()` once, before sharing the `Oco` with +/// other parts of your application (so that all future clones are `O(1)`), or simply use this as +/// if it is a [`Cow`] with an additional branch for reference-counted values. pub enum Oco<'a, T: ?Sized + ToOwned + 'a> { /// A static reference to a value. Borrowed(&'a T), @@ -46,7 +85,7 @@ impl<'a, T: ?Sized + ToOwned> Oco<'a, T> { /// # Examples /// ``` /// # use std::rc::Rc; - /// # use leptos_reactive::oco::Oco; + /// # use oco_ref::Oco; /// assert!(Oco::::Borrowed("Hello").is_borrowed()); /// assert!(!Oco::::Counted(Rc::from("Hello")).is_borrowed()); /// assert!(!Oco::::Owned("Hello".to_string()).is_borrowed()); @@ -59,7 +98,7 @@ impl<'a, T: ?Sized + ToOwned> Oco<'a, T> { /// # Examples /// ``` /// # use std::rc::Rc; - /// # use leptos_reactive::oco::Oco; + /// # use oco_ref::Oco; /// assert!(Oco::::Counted(Rc::from("Hello")).is_counted()); /// assert!(!Oco::::Borrowed("Hello").is_counted()); /// assert!(!Oco::::Owned("Hello".to_string()).is_counted()); @@ -72,7 +111,7 @@ impl<'a, T: ?Sized + ToOwned> Oco<'a, T> { /// # Examples /// ``` /// # use std::rc::Rc; - /// # use leptos_reactive::oco::Oco; + /// # use oco_ref::Oco; /// assert!(Oco::::Owned("Hello".to_string()).is_owned()); /// assert!(!Oco::::Borrowed("Hello").is_owned()); /// assert!(!Oco::::Counted(Rc::from("Hello")).is_owned()); @@ -130,7 +169,7 @@ impl Oco<'_, str> { /// Returns a `&str` slice of this [`Oco`]. /// # Examples /// ``` - /// # use leptos_reactive::oco::Oco; + /// # use oco_ref::Oco; /// let oco = Oco::::Borrowed("Hello"); /// let s: &str = oco.as_str(); /// assert_eq!(s, "Hello"); @@ -145,7 +184,7 @@ impl Oco<'_, CStr> { /// Returns a `&CStr` slice of this [`Oco`]. /// # Examples /// ``` - /// # use leptos_reactive::oco::Oco; + /// # use oco_ref::Oco; /// use std::ffi::CStr; /// /// let oco = @@ -163,7 +202,7 @@ impl Oco<'_, OsStr> { /// Returns a `&OsStr` slice of this [`Oco`]. /// # Examples /// ``` - /// # use leptos_reactive::oco::Oco; + /// # use oco_ref::Oco; /// use std::ffi::OsStr; /// /// let oco = Oco::::Borrowed(OsStr::new("Hello")); @@ -180,7 +219,7 @@ impl Oco<'_, Path> { /// Returns a `&Path` slice of this [`Oco`]. /// # Examples /// ``` - /// # use leptos_reactive::oco::Oco; + /// # use oco_ref::Oco; /// use std::path::Path; /// /// let oco = Oco::::Borrowed(Path::new("Hello")); @@ -200,7 +239,7 @@ where /// Returns a `&[T]` slice of this [`Oco`]. /// # Examples /// ``` - /// # use leptos_reactive::oco::Oco; + /// # use oco_ref::Oco; /// let oco = Oco::<[u8]>::Borrowed(b"Hello"); /// let s: &[u8] = oco.as_slice(); /// assert_eq!(s, b"Hello"); @@ -222,7 +261,7 @@ where /// # Examples /// [`String`] : /// ``` - /// # use leptos_reactive::oco::Oco; + /// # use oco_ref::Oco; /// let oco = Oco::::Owned("Hello".to_string()); /// let oco2 = oco.clone(); /// assert_eq!(oco, oco2); @@ -230,7 +269,7 @@ where /// ``` /// [`Vec`] : /// ``` - /// # use leptos_reactive::oco::Oco; + /// # use oco_ref::Oco; /// let oco = Oco::<[u8]>::Owned(b"Hello".to_vec()); /// let oco2 = oco.clone(); /// assert_eq!(oco, oco2); @@ -254,7 +293,7 @@ where /// was previously [`Oco::Owned`]. /// # Examples /// ``` - /// # use leptos_reactive::oco::Oco; + /// # use oco_ref::Oco; /// let mut oco1 = Oco::::Owned("Hello".to_string()); /// let oco2 = oco1.clone_inplace(); /// assert_eq!(oco1, oco2);