bevy_asset/
handle.rs

1use crate::{
2    meta::MetaTransform, Asset, AssetId, AssetIndexAllocator, AssetPath, InternalAssetId,
3    UntypedAssetId,
4};
5use alloc::sync::Arc;
6use bevy_reflect::{std_traits::ReflectDefault, Reflect, TypePath};
7use core::{
8    any::TypeId,
9    hash::{Hash, Hasher},
10};
11use crossbeam_channel::{Receiver, Sender};
12use derive_more::derive::{Display, Error};
13use disqualified::ShortName;
14use uuid::Uuid;
15
16/// Provides [`Handle`] and [`UntypedHandle`] _for a specific asset type_.
17/// This should _only_ be used for one specific asset type.
18#[derive(Clone)]
19pub struct AssetHandleProvider {
20    pub(crate) allocator: Arc<AssetIndexAllocator>,
21    pub(crate) drop_sender: Sender<DropEvent>,
22    pub(crate) drop_receiver: Receiver<DropEvent>,
23    pub(crate) type_id: TypeId,
24}
25
26#[derive(Debug)]
27pub(crate) struct DropEvent {
28    pub(crate) id: InternalAssetId,
29    pub(crate) asset_server_managed: bool,
30}
31
32impl AssetHandleProvider {
33    pub(crate) fn new(type_id: TypeId, allocator: Arc<AssetIndexAllocator>) -> Self {
34        let (drop_sender, drop_receiver) = crossbeam_channel::unbounded();
35        Self {
36            type_id,
37            allocator,
38            drop_sender,
39            drop_receiver,
40        }
41    }
42
43    /// Reserves a new strong [`UntypedHandle`] (with a new [`UntypedAssetId`]). The stored [`Asset`] [`TypeId`] in the
44    /// [`UntypedHandle`] will match the [`Asset`] [`TypeId`] assigned to this [`AssetHandleProvider`].
45    pub fn reserve_handle(&self) -> UntypedHandle {
46        let index = self.allocator.reserve();
47        UntypedHandle::Strong(self.get_handle(InternalAssetId::Index(index), false, None, None))
48    }
49
50    pub(crate) fn get_handle(
51        &self,
52        id: InternalAssetId,
53        asset_server_managed: bool,
54        path: Option<AssetPath<'static>>,
55        meta_transform: Option<MetaTransform>,
56    ) -> Arc<StrongHandle> {
57        Arc::new(StrongHandle {
58            id: id.untyped(self.type_id),
59            drop_sender: self.drop_sender.clone(),
60            meta_transform,
61            path,
62            asset_server_managed,
63        })
64    }
65
66    pub(crate) fn reserve_handle_internal(
67        &self,
68        asset_server_managed: bool,
69        path: Option<AssetPath<'static>>,
70        meta_transform: Option<MetaTransform>,
71    ) -> Arc<StrongHandle> {
72        let index = self.allocator.reserve();
73        self.get_handle(
74            InternalAssetId::Index(index),
75            asset_server_managed,
76            path,
77            meta_transform,
78        )
79    }
80}
81
82/// The internal "strong" [`Asset`] handle storage for [`Handle::Strong`] and [`UntypedHandle::Strong`]. When this is dropped,
83/// the [`Asset`] will be freed. It also stores some asset metadata for easy access from handles.
84#[derive(TypePath)]
85pub struct StrongHandle {
86    pub(crate) id: UntypedAssetId,
87    pub(crate) asset_server_managed: bool,
88    pub(crate) path: Option<AssetPath<'static>>,
89    /// Modifies asset meta. This is stored on the handle because it is:
90    /// 1. configuration tied to the lifetime of a specific asset load
91    /// 2. configuration that must be repeatable when the asset is hot-reloaded
92    pub(crate) meta_transform: Option<MetaTransform>,
93    pub(crate) drop_sender: Sender<DropEvent>,
94}
95
96impl Drop for StrongHandle {
97    fn drop(&mut self) {
98        let _ = self.drop_sender.send(DropEvent {
99            id: self.id.internal(),
100            asset_server_managed: self.asset_server_managed,
101        });
102    }
103}
104
105impl core::fmt::Debug for StrongHandle {
106    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
107        f.debug_struct("StrongHandle")
108            .field("id", &self.id)
109            .field("asset_server_managed", &self.asset_server_managed)
110            .field("path", &self.path)
111            .field("drop_sender", &self.drop_sender)
112            .finish()
113    }
114}
115
116/// A strong or weak handle to a specific [`Asset`]. If a [`Handle`] is [`Handle::Strong`], the [`Asset`] will be kept
117/// alive until the [`Handle`] is dropped. If a [`Handle`] is [`Handle::Weak`], it does not necessarily reference a live [`Asset`],
118/// nor will it keep assets alive.
119///
120/// [`Handle`] can be cloned. If a [`Handle::Strong`] is cloned, the referenced [`Asset`] will not be freed until _all_ instances
121/// of the [`Handle`] are dropped.
122///
123/// [`Handle::Strong`] also provides access to useful [`Asset`] metadata, such as the [`AssetPath`] (if it exists).
124#[derive(Reflect)]
125#[reflect(Default, Debug, Hash, PartialEq)]
126pub enum Handle<A: Asset> {
127    /// A "strong" reference to a live (or loading) [`Asset`]. If a [`Handle`] is [`Handle::Strong`], the [`Asset`] will be kept
128    /// alive until the [`Handle`] is dropped. Strong handles also provide access to additional asset metadata.
129    Strong(Arc<StrongHandle>),
130    /// A "weak" reference to an [`Asset`]. If a [`Handle`] is [`Handle::Weak`], it does not necessarily reference a live [`Asset`],
131    /// nor will it keep assets alive.
132    Weak(AssetId<A>),
133}
134
135impl<T: Asset> Clone for Handle<T> {
136    fn clone(&self) -> Self {
137        match self {
138            Handle::Strong(handle) => Handle::Strong(handle.clone()),
139            Handle::Weak(id) => Handle::Weak(*id),
140        }
141    }
142}
143
144impl<A: Asset> Handle<A> {
145    /// Create a new [`Handle::Weak`] with the given [`u128`] encoding of a [`Uuid`].
146    pub const fn weak_from_u128(value: u128) -> Self {
147        Handle::Weak(AssetId::Uuid {
148            uuid: Uuid::from_u128(value),
149        })
150    }
151
152    /// Returns the [`AssetId`] of this [`Asset`].
153    #[inline]
154    pub fn id(&self) -> AssetId<A> {
155        match self {
156            Handle::Strong(handle) => handle.id.typed_unchecked(),
157            Handle::Weak(id) => *id,
158        }
159    }
160
161    /// Returns the path if this is (1) a strong handle and (2) the asset has a path
162    #[inline]
163    pub fn path(&self) -> Option<&AssetPath<'static>> {
164        match self {
165            Handle::Strong(handle) => handle.path.as_ref(),
166            Handle::Weak(_) => None,
167        }
168    }
169
170    /// Returns `true` if this is a weak handle.
171    #[inline]
172    pub fn is_weak(&self) -> bool {
173        matches!(self, Handle::Weak(_))
174    }
175
176    /// Returns `true` if this is a strong handle.
177    #[inline]
178    pub fn is_strong(&self) -> bool {
179        matches!(self, Handle::Strong(_))
180    }
181
182    /// Creates a [`Handle::Weak`] clone of this [`Handle`], which will not keep the referenced [`Asset`] alive.
183    #[inline]
184    pub fn clone_weak(&self) -> Self {
185        match self {
186            Handle::Strong(handle) => Handle::Weak(handle.id.typed_unchecked::<A>()),
187            Handle::Weak(id) => Handle::Weak(*id),
188        }
189    }
190
191    /// Converts this [`Handle`] to an "untyped" / "generic-less" [`UntypedHandle`], which stores the [`Asset`] type information
192    /// _inside_ [`UntypedHandle`]. This will return [`UntypedHandle::Strong`] for [`Handle::Strong`] and [`UntypedHandle::Weak`] for
193    /// [`Handle::Weak`].
194    #[inline]
195    pub fn untyped(self) -> UntypedHandle {
196        self.into()
197    }
198}
199
200impl<A: Asset> Default for Handle<A> {
201    fn default() -> Self {
202        Handle::Weak(AssetId::default())
203    }
204}
205
206impl<A: Asset> core::fmt::Debug for Handle<A> {
207    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
208        let name = ShortName::of::<A>();
209        match self {
210            Handle::Strong(handle) => {
211                write!(
212                    f,
213                    "StrongHandle<{name}>{{ id: {:?}, path: {:?} }}",
214                    handle.id.internal(),
215                    handle.path
216                )
217            }
218            Handle::Weak(id) => write!(f, "WeakHandle<{name}>({:?})", id.internal()),
219        }
220    }
221}
222
223impl<A: Asset> Hash for Handle<A> {
224    #[inline]
225    fn hash<H: Hasher>(&self, state: &mut H) {
226        self.id().hash(state);
227    }
228}
229
230impl<A: Asset> PartialOrd for Handle<A> {
231    fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
232        Some(self.cmp(other))
233    }
234}
235
236impl<A: Asset> Ord for Handle<A> {
237    fn cmp(&self, other: &Self) -> core::cmp::Ordering {
238        self.id().cmp(&other.id())
239    }
240}
241
242impl<A: Asset> PartialEq for Handle<A> {
243    #[inline]
244    fn eq(&self, other: &Self) -> bool {
245        self.id() == other.id()
246    }
247}
248
249impl<A: Asset> Eq for Handle<A> {}
250
251impl<A: Asset> From<&Handle<A>> for AssetId<A> {
252    #[inline]
253    fn from(value: &Handle<A>) -> Self {
254        value.id()
255    }
256}
257
258impl<A: Asset> From<&Handle<A>> for UntypedAssetId {
259    #[inline]
260    fn from(value: &Handle<A>) -> Self {
261        value.id().into()
262    }
263}
264
265impl<A: Asset> From<&mut Handle<A>> for AssetId<A> {
266    #[inline]
267    fn from(value: &mut Handle<A>) -> Self {
268        value.id()
269    }
270}
271
272impl<A: Asset> From<&mut Handle<A>> for UntypedAssetId {
273    #[inline]
274    fn from(value: &mut Handle<A>) -> Self {
275        value.id().into()
276    }
277}
278
279/// An untyped variant of [`Handle`], which internally stores the [`Asset`] type information at runtime
280/// as a [`TypeId`] instead of encoding it in the compile-time type. This allows handles across [`Asset`] types
281/// to be stored together and compared.
282///
283/// See [`Handle`] for more information.
284#[derive(Clone)]
285pub enum UntypedHandle {
286    Strong(Arc<StrongHandle>),
287    Weak(UntypedAssetId),
288}
289
290impl UntypedHandle {
291    /// Returns the [`UntypedAssetId`] for the referenced asset.
292    #[inline]
293    pub fn id(&self) -> UntypedAssetId {
294        match self {
295            UntypedHandle::Strong(handle) => handle.id,
296            UntypedHandle::Weak(id) => *id,
297        }
298    }
299
300    /// Returns the path if this is (1) a strong handle and (2) the asset has a path
301    #[inline]
302    pub fn path(&self) -> Option<&AssetPath<'static>> {
303        match self {
304            UntypedHandle::Strong(handle) => handle.path.as_ref(),
305            UntypedHandle::Weak(_) => None,
306        }
307    }
308
309    /// Creates an [`UntypedHandle::Weak`] clone of this [`UntypedHandle`], which will not keep the referenced [`Asset`] alive.
310    #[inline]
311    pub fn clone_weak(&self) -> UntypedHandle {
312        match self {
313            UntypedHandle::Strong(handle) => UntypedHandle::Weak(handle.id),
314            UntypedHandle::Weak(id) => UntypedHandle::Weak(*id),
315        }
316    }
317
318    /// Returns the [`TypeId`] of the referenced [`Asset`].
319    #[inline]
320    pub fn type_id(&self) -> TypeId {
321        match self {
322            UntypedHandle::Strong(handle) => handle.id.type_id(),
323            UntypedHandle::Weak(id) => id.type_id(),
324        }
325    }
326
327    /// Converts to a typed Handle. This _will not check if the target Handle type matches_.
328    #[inline]
329    pub fn typed_unchecked<A: Asset>(self) -> Handle<A> {
330        match self {
331            UntypedHandle::Strong(handle) => Handle::Strong(handle),
332            UntypedHandle::Weak(id) => Handle::Weak(id.typed_unchecked::<A>()),
333        }
334    }
335
336    /// Converts to a typed Handle. This will check the type when compiled with debug asserts, but it
337    ///  _will not check if the target Handle type matches in release builds_. Use this as an optimization
338    /// when you want some degree of validation at dev-time, but you are also very certain that the type
339    /// actually matches.
340    #[inline]
341    pub fn typed_debug_checked<A: Asset>(self) -> Handle<A> {
342        debug_assert_eq!(
343            self.type_id(),
344            TypeId::of::<A>(),
345            "The target Handle<A>'s TypeId does not match the TypeId of this UntypedHandle"
346        );
347        match self {
348            UntypedHandle::Strong(handle) => Handle::Strong(handle),
349            UntypedHandle::Weak(id) => Handle::Weak(id.typed_unchecked::<A>()),
350        }
351    }
352
353    /// Converts to a typed Handle. This will panic if the internal [`TypeId`] does not match the given asset type `A`
354    #[inline]
355    pub fn typed<A: Asset>(self) -> Handle<A> {
356        let Ok(handle) = self.try_typed() else {
357            panic!(
358                "The target Handle<{}>'s TypeId does not match the TypeId of this UntypedHandle",
359                core::any::type_name::<A>()
360            )
361        };
362
363        handle
364    }
365
366    /// Converts to a typed Handle. This will panic if the internal [`TypeId`] does not match the given asset type `A`
367    #[inline]
368    pub fn try_typed<A: Asset>(self) -> Result<Handle<A>, UntypedAssetConversionError> {
369        Handle::try_from(self)
370    }
371
372    /// The "meta transform" for the strong handle. This will only be [`Some`] if the handle is strong and there is a meta transform
373    /// associated with it.
374    #[inline]
375    pub fn meta_transform(&self) -> Option<&MetaTransform> {
376        match self {
377            UntypedHandle::Strong(handle) => handle.meta_transform.as_ref(),
378            UntypedHandle::Weak(_) => None,
379        }
380    }
381}
382
383impl PartialEq for UntypedHandle {
384    #[inline]
385    fn eq(&self, other: &Self) -> bool {
386        self.id() == other.id() && self.type_id() == other.type_id()
387    }
388}
389
390impl Eq for UntypedHandle {}
391
392impl Hash for UntypedHandle {
393    #[inline]
394    fn hash<H: Hasher>(&self, state: &mut H) {
395        self.id().hash(state);
396    }
397}
398
399impl core::fmt::Debug for UntypedHandle {
400    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
401        match self {
402            UntypedHandle::Strong(handle) => {
403                write!(
404                    f,
405                    "StrongHandle{{ type_id: {:?}, id: {:?}, path: {:?} }}",
406                    handle.id.type_id(),
407                    handle.id.internal(),
408                    handle.path
409                )
410            }
411            UntypedHandle::Weak(id) => write!(
412                f,
413                "WeakHandle{{ type_id: {:?}, id: {:?} }}",
414                id.type_id(),
415                id.internal()
416            ),
417        }
418    }
419}
420
421impl PartialOrd for UntypedHandle {
422    fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
423        if self.type_id() == other.type_id() {
424            self.id().partial_cmp(&other.id())
425        } else {
426            None
427        }
428    }
429}
430
431impl From<&UntypedHandle> for UntypedAssetId {
432    #[inline]
433    fn from(value: &UntypedHandle) -> Self {
434        value.id()
435    }
436}
437
438// Cross Operations
439
440impl<A: Asset> PartialEq<UntypedHandle> for Handle<A> {
441    #[inline]
442    fn eq(&self, other: &UntypedHandle) -> bool {
443        TypeId::of::<A>() == other.type_id() && self.id() == other.id()
444    }
445}
446
447impl<A: Asset> PartialEq<Handle<A>> for UntypedHandle {
448    #[inline]
449    fn eq(&self, other: &Handle<A>) -> bool {
450        other.eq(self)
451    }
452}
453
454impl<A: Asset> PartialOrd<UntypedHandle> for Handle<A> {
455    #[inline]
456    fn partial_cmp(&self, other: &UntypedHandle) -> Option<core::cmp::Ordering> {
457        if TypeId::of::<A>() != other.type_id() {
458            None
459        } else {
460            self.id().partial_cmp(&other.id())
461        }
462    }
463}
464
465impl<A: Asset> PartialOrd<Handle<A>> for UntypedHandle {
466    #[inline]
467    fn partial_cmp(&self, other: &Handle<A>) -> Option<core::cmp::Ordering> {
468        Some(other.partial_cmp(self)?.reverse())
469    }
470}
471
472impl<A: Asset> From<Handle<A>> for UntypedHandle {
473    fn from(value: Handle<A>) -> Self {
474        match value {
475            Handle::Strong(handle) => UntypedHandle::Strong(handle),
476            Handle::Weak(id) => UntypedHandle::Weak(id.into()),
477        }
478    }
479}
480
481impl<A: Asset> TryFrom<UntypedHandle> for Handle<A> {
482    type Error = UntypedAssetConversionError;
483
484    fn try_from(value: UntypedHandle) -> Result<Self, Self::Error> {
485        let found = value.type_id();
486        let expected = TypeId::of::<A>();
487
488        if found != expected {
489            return Err(UntypedAssetConversionError::TypeIdMismatch { expected, found });
490        }
491
492        match value {
493            UntypedHandle::Strong(handle) => Ok(Handle::Strong(handle)),
494            UntypedHandle::Weak(id) => {
495                let Ok(id) = id.try_into() else {
496                    return Err(UntypedAssetConversionError::TypeIdMismatch { expected, found });
497                };
498                Ok(Handle::Weak(id))
499            }
500        }
501    }
502}
503
504/// Errors preventing the conversion of to/from an [`UntypedHandle`] and a [`Handle`].
505#[derive(Error, Display, Debug, PartialEq, Clone)]
506#[non_exhaustive]
507pub enum UntypedAssetConversionError {
508    /// Caused when trying to convert an [`UntypedHandle`] into a [`Handle`] of the wrong type.
509    #[display(
510        "This UntypedHandle is for {found:?} and cannot be converted into a Handle<{expected:?}>"
511    )]
512    TypeIdMismatch { expected: TypeId, found: TypeId },
513}
514
515#[cfg(test)]
516mod tests {
517    use bevy_reflect::PartialReflect;
518
519    use super::*;
520
521    type TestAsset = ();
522
523    const UUID_1: Uuid = Uuid::from_u128(123);
524    const UUID_2: Uuid = Uuid::from_u128(456);
525
526    /// Simple utility to directly hash a value using a fixed hasher
527    fn hash<T: Hash>(data: &T) -> u64 {
528        let mut hasher = bevy_utils::AHasher::default();
529        data.hash(&mut hasher);
530        hasher.finish()
531    }
532
533    /// Typed and Untyped `Handles` should be equivalent to each other and themselves
534    #[test]
535    fn equality() {
536        let typed = AssetId::<TestAsset>::Uuid { uuid: UUID_1 };
537        let untyped = UntypedAssetId::Uuid {
538            type_id: TypeId::of::<TestAsset>(),
539            uuid: UUID_1,
540        };
541
542        let typed = Handle::Weak(typed);
543        let untyped = UntypedHandle::Weak(untyped);
544
545        assert_eq!(
546            Ok(typed.clone()),
547            Handle::<TestAsset>::try_from(untyped.clone())
548        );
549        assert_eq!(UntypedHandle::from(typed.clone()), untyped);
550        assert_eq!(typed, untyped);
551    }
552
553    /// Typed and Untyped `Handles` should be orderable amongst each other and themselves
554    #[allow(clippy::cmp_owned)]
555    #[test]
556    fn ordering() {
557        assert!(UUID_1 < UUID_2);
558
559        let typed_1 = AssetId::<TestAsset>::Uuid { uuid: UUID_1 };
560        let typed_2 = AssetId::<TestAsset>::Uuid { uuid: UUID_2 };
561        let untyped_1 = UntypedAssetId::Uuid {
562            type_id: TypeId::of::<TestAsset>(),
563            uuid: UUID_1,
564        };
565        let untyped_2 = UntypedAssetId::Uuid {
566            type_id: TypeId::of::<TestAsset>(),
567            uuid: UUID_2,
568        };
569
570        let typed_1 = Handle::Weak(typed_1);
571        let typed_2 = Handle::Weak(typed_2);
572        let untyped_1 = UntypedHandle::Weak(untyped_1);
573        let untyped_2 = UntypedHandle::Weak(untyped_2);
574
575        assert!(typed_1 < typed_2);
576        assert!(untyped_1 < untyped_2);
577
578        assert!(UntypedHandle::from(typed_1.clone()) < untyped_2);
579        assert!(untyped_1 < UntypedHandle::from(typed_2.clone()));
580
581        assert!(Handle::<TestAsset>::try_from(untyped_1.clone()).unwrap() < typed_2);
582        assert!(typed_1 < Handle::<TestAsset>::try_from(untyped_2.clone()).unwrap());
583
584        assert!(typed_1 < untyped_2);
585        assert!(untyped_1 < typed_2);
586    }
587
588    /// Typed and Untyped `Handles` should be equivalently hashable to each other and themselves
589    #[test]
590    fn hashing() {
591        let typed = AssetId::<TestAsset>::Uuid { uuid: UUID_1 };
592        let untyped = UntypedAssetId::Uuid {
593            type_id: TypeId::of::<TestAsset>(),
594            uuid: UUID_1,
595        };
596
597        let typed = Handle::Weak(typed);
598        let untyped = UntypedHandle::Weak(untyped);
599
600        assert_eq!(
601            hash(&typed),
602            hash(&Handle::<TestAsset>::try_from(untyped.clone()).unwrap())
603        );
604        assert_eq!(hash(&UntypedHandle::from(typed.clone())), hash(&untyped));
605        assert_eq!(hash(&typed), hash(&untyped));
606    }
607
608    /// Typed and Untyped `Handles` should be interchangeable
609    #[test]
610    fn conversion() {
611        let typed = AssetId::<TestAsset>::Uuid { uuid: UUID_1 };
612        let untyped = UntypedAssetId::Uuid {
613            type_id: TypeId::of::<TestAsset>(),
614            uuid: UUID_1,
615        };
616
617        let typed = Handle::Weak(typed);
618        let untyped = UntypedHandle::Weak(untyped);
619
620        assert_eq!(typed, Handle::try_from(untyped.clone()).unwrap());
621        assert_eq!(UntypedHandle::from(typed.clone()), untyped);
622    }
623
624    /// `Reflect::clone_value` should increase the strong count of a strong handle
625    #[test]
626    fn strong_handle_reflect_clone() {
627        use crate::{AssetApp, AssetPlugin, Assets, VisitAssetDependencies};
628        use bevy_app::App;
629        use bevy_reflect::FromReflect;
630
631        #[derive(Reflect)]
632        struct MyAsset {
633            value: u32,
634        }
635        impl Asset for MyAsset {}
636        impl VisitAssetDependencies for MyAsset {
637            fn visit_dependencies(&self, _visit: &mut impl FnMut(UntypedAssetId)) {}
638        }
639
640        let mut app = App::new();
641        app.add_plugins(AssetPlugin::default())
642            .init_asset::<MyAsset>();
643        let mut assets = app.world_mut().resource_mut::<Assets<MyAsset>>();
644
645        let handle: Handle<MyAsset> = assets.add(MyAsset { value: 1 });
646        match &handle {
647            Handle::Strong(strong) => {
648                assert_eq!(
649                    Arc::strong_count(strong),
650                    1,
651                    "Inserting the asset should result in a strong count of 1"
652                );
653
654                let reflected: &dyn Reflect = &handle;
655                let cloned_handle: Box<dyn PartialReflect> = reflected.clone_value();
656
657                assert_eq!(
658                    Arc::strong_count(strong),
659                    2,
660                    "Cloning the handle with reflect should increase the strong count to 2"
661                );
662
663                let from_reflect_handle: Handle<MyAsset> =
664                    FromReflect::from_reflect(&*cloned_handle).unwrap();
665
666                assert_eq!(Arc::strong_count(strong), 3, "Converting the reflected value back to a handle should increase the strong count to 3");
667                assert!(
668                    from_reflect_handle.is_strong(),
669                    "The cloned handle should still be strong"
670                );
671            }
672            _ => panic!("Expected a strong handle"),
673        }
674    }
675}