aws_smithy_runtime_api/
shared.rs

1/*
2 * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3 * SPDX-License-Identifier: Apache-2.0
4 */
5
6//! Conversion traits for converting an unshared type into a shared type.
7//!
8//! The standard [`From`]/[`Into`] traits can't be
9//! used for this purpose due to the blanket implementation of `Into`.
10//!
11//! This implementation also adds a [`maybe_shared`] method and [`impl_shared_conversions`](crate::impl_shared_conversions)
12//! macro to trivially avoid nesting shared types with other shared types.
13//!
14//! # What is a shared type?
15//!
16//! A shared type is a new-type around a `Send + Sync` reference counting smart pointer
17//! (i.e., an [`Arc`](std::sync::Arc)) around an object-safe trait. Shared types are
18//! used to share a trait object among multiple threads/clients/requests.
19#![cfg_attr(
20    feature = "client",
21    doc = "
22For example, [`SharedHttpConnector`](crate::client::http::SharedHttpConnector), is
23a shared type for the [`HttpConnector`](crate::client::http::HttpConnector) trait,
24which allows for sharing a single HTTP connector instance (and its connection pool) among multiple clients.
25"
26)]
27//!
28//! A shared type implements the [`FromUnshared`] trait, which allows any implementation
29//! of the trait it wraps to easily be converted into it.
30//!
31#![cfg_attr(
32    feature = "client",
33    doc = "
34To illustrate, let's examine the
35[`RuntimePlugin`](crate::client::runtime_plugin::RuntimePlugin)/[`SharedRuntimePlugin`](crate::client::runtime_plugin::SharedRuntimePlugin)
36duo.
37The following instantiates a concrete implementation of the `RuntimePlugin` trait.
38We can do `RuntimePlugin` things on this instance.
39
40```rust,no_run
41use aws_smithy_runtime_api::client::runtime_plugin::StaticRuntimePlugin;
42
43let some_plugin = StaticRuntimePlugin::new();
44```
45
46We can convert this instance into a shared type in two different ways.
47
48```rust,no_run
49# use aws_smithy_runtime_api::client::runtime_plugin::StaticRuntimePlugin;
50# let some_plugin = StaticRuntimePlugin::new();
51use aws_smithy_runtime_api::client::runtime_plugin::SharedRuntimePlugin;
52use aws_smithy_runtime_api::shared::{IntoShared, FromUnshared};
53
54// Using the `IntoShared` trait
55let shared: SharedRuntimePlugin = some_plugin.into_shared();
56
57// Using the `FromUnshared` trait:
58# let some_plugin = StaticRuntimePlugin::new();
59let shared = SharedRuntimePlugin::from_unshared(some_plugin);
60```
61
62The `IntoShared` trait is useful for making functions that take any `RuntimePlugin` impl and convert it to a shared type.
63For example, this function will convert the given `plugin` argument into a `SharedRuntimePlugin`.
64
65```rust,no_run
66# use aws_smithy_runtime_api::client::runtime_plugin::{RuntimePlugin, SharedRuntimePlugin};
67use aws_smithy_runtime_api::shared::IntoShared;
68
69fn take_shared(plugin: impl RuntimePlugin + 'static) {
70    let _plugin: SharedRuntimePlugin = plugin.into_shared();
71}
72```
73
74This can be called with different types, and even if a `SharedRuntimePlugin` is passed in, it won't nest that
75`SharedRuntimePlugin` inside of another `SharedRuntimePlugin`.
76
77```rust,no_run
78# use aws_smithy_runtime_api::client::runtime_plugin::{RuntimePlugin, SharedRuntimePlugin, StaticRuntimePlugin};
79# use aws_smithy_runtime_api::shared::{IntoShared, FromUnshared};
80# fn take_shared(plugin: impl RuntimePlugin + 'static) {
81#     let _plugin: SharedRuntimePlugin = plugin.into_shared();
82# }
83// Automatically converts it to `SharedRuntimePlugin(StaticRuntimePlugin)`
84take_shared(StaticRuntimePlugin::new());
85
86// This is OK.
87// It create a `SharedRuntimePlugin(StaticRuntimePlugin))`
88// instead of a nested `SharedRuntimePlugin(SharedRuntimePlugin(StaticRuntimePlugin)))`
89take_shared(SharedRuntimePlugin::new(StaticRuntimePlugin::new()));
90```
91"
92)]
93
94use std::any::{Any, TypeId};
95
96/// Like the `From` trait, but for converting to a shared type.
97///
98/// See the [module docs](crate::shared) for information about shared types.
99pub trait FromUnshared<Unshared> {
100    /// Creates a shared type from an unshared type.
101    fn from_unshared(value: Unshared) -> Self;
102}
103
104/// Like the `Into` trait, but for (efficiently) converting into a shared type.
105///
106/// If the type is already a shared type, it won't be nested in another shared type.
107///
108/// See the [module docs](crate::shared) for information about shared types.
109pub trait IntoShared<Shared> {
110    /// Creates a shared type from an unshared type.
111    fn into_shared(self) -> Shared;
112}
113
114impl<Unshared, Shared> IntoShared<Shared> for Unshared
115where
116    Shared: FromUnshared<Unshared>,
117{
118    fn into_shared(self) -> Shared {
119        FromUnshared::from_unshared(self)
120    }
121}
122
123/// Given a `value`, determine if that value is already shared. If it is, return it. Otherwise, wrap it in a shared type.
124///
125/// See the [module docs](crate::shared) for information about shared types.
126pub fn maybe_shared<Shared, MaybeShared, F>(value: MaybeShared, ctor: F) -> Shared
127where
128    Shared: 'static,
129    MaybeShared: IntoShared<Shared> + 'static,
130    F: FnOnce(MaybeShared) -> Shared,
131{
132    // Check if the type is already a shared type
133    if TypeId::of::<MaybeShared>() == TypeId::of::<Shared>() {
134        // Convince the compiler it is already a shared type and return it
135        let mut placeholder = Some(value);
136        let value: Shared = (&mut placeholder as &mut dyn Any)
137            .downcast_mut::<Option<Shared>>()
138            .expect("type checked above")
139            .take()
140            .expect("set to Some above");
141        value
142    } else {
143        (ctor)(value)
144    }
145}
146
147/// Implements `FromUnshared` for a shared type.
148///
149/// See the [`shared` module docs](crate::shared) for information about shared types.
150///
151/// # Example
152/// ```rust,no_run
153/// use aws_smithy_runtime_api::impl_shared_conversions;
154/// use std::sync::Arc;
155///
156/// trait Thing {}
157///
158/// struct Thingamajig;
159/// impl Thing for Thingamajig {}
160///
161/// struct SharedThing(Arc<dyn Thing>);
162/// impl Thing for SharedThing {}
163/// impl SharedThing {
164///     fn new(thing: impl Thing + 'static) -> Self {
165///         Self(Arc::new(thing))
166///     }
167/// }
168/// impl_shared_conversions!(convert SharedThing from Thing using SharedThing::new);
169/// ```
170#[macro_export]
171macro_rules! impl_shared_conversions {
172    (convert $shared_type:ident from $unshared_trait:ident using $ctor:expr) => {
173        impl<T> $crate::shared::FromUnshared<T> for $shared_type
174        where
175            T: $unshared_trait + 'static,
176        {
177            fn from_unshared(value: T) -> Self {
178                $crate::shared::maybe_shared(value, $ctor)
179            }
180        }
181    };
182}
183
184// TODO(https://github.com/smithy-lang/smithy-rs/issues/3016): Move these impls once aws-smithy-async is merged into aws-smithy-runtime-api
185mod async_impls {
186    use aws_smithy_async::rt::sleep::{AsyncSleep, SharedAsyncSleep};
187    use aws_smithy_async::time::{SharedTimeSource, TimeSource};
188    impl_shared_conversions!(convert SharedAsyncSleep from AsyncSleep using SharedAsyncSleep::new);
189    impl_shared_conversions!(convert SharedTimeSource from TimeSource using SharedTimeSource::new);
190}
191
192#[cfg(test)]
193mod tests {
194    use super::*;
195    use std::fmt;
196    use std::sync::Arc;
197
198    trait Thing: fmt::Debug {}
199
200    #[derive(Debug)]
201    struct Thingamajig;
202    impl Thing for Thingamajig {}
203
204    #[derive(Debug)]
205    struct SharedThing(#[allow(dead_code)] Arc<dyn Thing>);
206    impl Thing for SharedThing {}
207    impl SharedThing {
208        fn new(thing: impl Thing + 'static) -> Self {
209            Self(Arc::new(thing))
210        }
211    }
212    impl_shared_conversions!(convert SharedThing from Thing using SharedThing::new);
213
214    #[test]
215    fn test() {
216        let thing = Thingamajig;
217        assert_eq!("Thingamajig", format!("{thing:?}"), "precondition");
218
219        let shared_thing: SharedThing = thing.into_shared();
220        assert_eq!(
221            "SharedThing(Thingamajig)",
222            format!("{shared_thing:?}"),
223            "precondition"
224        );
225
226        let very_shared_thing: SharedThing = shared_thing.into_shared();
227        assert_eq!(
228            "SharedThing(Thingamajig)",
229            format!("{very_shared_thing:?}"),
230            "it should not nest the shared thing in another shared thing"
231        );
232    }
233}