From c4f51b749a99993d72c3604b7713ec674a1354e2 Mon Sep 17 00:00:00 2001 From: Alexander Capehart Date: Mon, 17 Feb 2025 10:10:50 -0700 Subject: [PATCH] musikr: document this contract --- musikr/src/main/jni/src/taglib/this.rs | 74 ++++++++++++++++++++++++++ 1 file changed, 74 insertions(+) diff --git a/musikr/src/main/jni/src/taglib/this.rs b/musikr/src/main/jni/src/taglib/this.rs index 6c03cb956..9655afee0 100644 --- a/musikr/src/main/jni/src/taglib/this.rs +++ b/musikr/src/main/jni/src/taglib/this.rs @@ -2,25 +2,65 @@ use std::marker::PhantomData; use std::pin::Pin; use cxx::{UniquePtr, memory::UniquePtrTarget}; +/// A taglib-FFI-specific trait representing a C++ object returned by the library. +/// +/// This trait is used to provide a temporary pin of the object for use in C++ +/// member function calls. +/// +/// `This` instances must hold the following contract: +/// - This object will remain valid as long as TagLib's FileRef object is valid, +/// and will be dropped when the FileRef is dropped. +/// - This object will not move or be mutated over the FileRef's lifetime, this way +/// it can be temporarily pinned for use as a `this` pointer. pub trait This<'file_ref, T> { + /// Temporarily pin the object for use in C++ member function calls. + /// + /// When C++ member functions are called, the object is temporarily pinned + /// as a `this` pointer. In turn, cxx requires a `Pin<&T>` to be used in it's + /// member function bindings, hence this method. + /// + /// This is safe to call assuming the contract of `This` is upheld. fn pin(&self) -> Pin<&T>; } +/// A taglib-FFI-specific trait representing a C++ object returned by the library. +/// +/// This trait is used to provide a temporary pin of the object for use in C++ +/// member function calls. +/// +/// `ThisMut` instances must hold the following contract: +/// - This object will remain valid as long as TagLib's FileRef object is valid, +/// and will be dropped when the FileRef is dropped. +/// - This object will not move over the FileRef's lifetime, this way it can be +/// temporarily pinned for use as a `this` pointer. pub trait ThisMut<'file_ref, T> : This<'file_ref, T> { fn pin_mut(&mut self) -> Pin<&mut T>; } +/// A [This] instance that is a reference to a C++ object. pub struct RefThis<'file_ref, T> { this: &'file_ref T } impl<'file_ref, T> RefThis<'file_ref, T> { + /// Create a new [RefThis] from a reference to a C++ object. + /// + /// This is safe to call assuming the contract of [This] is upheld. Since this + /// contract cannot be enforced by the Rust compiler, it is the caller's + /// responsibility to ensure that the reference is valid for the lifetime of + /// the `'file_ref` parameter. More or less, if it comes from the TagLib FFI + /// interface, it is safe to use this. pub unsafe fn new(this: &'file_ref T) -> Self { // Rough informal contact is that the reference points to a C++ object // that will live and not move for as long as 'file_ref. Self { this } } + /// Get a pointer to the C++ object. + /// + /// This can be used to pass the object to FFI functions that take a pointer. + /// + /// This is safe to call assuming the contract of [This] is upheld. pub fn ptr(&self) -> *const T { self.this as *const T } @@ -32,19 +72,39 @@ impl<'file_ref, T> This<'file_ref, T> for RefThis<'file_ref, T> { } } +/// A [ThisMut] instance that is a reference to a C++ object. +/// +/// This is similar to [RefThis], but allows mutating the object. pub struct RefThisMut<'file_ref, T> { this: &'file_ref mut T, } impl<'file_ref, T> RefThisMut<'file_ref, T> { + /// Create a new [RefThisMut] from a reference to a C++ object. + /// + /// This is safe to call assuming the contract of [ThisMut] is upheld. Since + /// this contract cannot be enforced by the Rust compiler, it is the caller's + /// responsibility to ensure that the reference is valid for the lifetime of + /// the `'file_ref` parameter. More or less, if it comes from the TagLib FFI + /// interface, it is safe to use this. pub unsafe fn new(this: &'file_ref mut T) -> Self { Self { this } } + /// Get a pointer to the C++ object. + /// + /// This can be used to pass the object to FFI functions that take a pointer. + /// + /// This is safe to call assuming the contract of [ThisMut] is upheld. pub fn ptr(&self) -> *const T { self.this as *const T } + /// Get a pointer to the C++ object. + /// + /// This can be used to pass the object to FFI functions that take a pointer. + /// + /// This is safe to call assuming the contract of [ThisMut] is upheld. pub fn ptr_mut(&mut self) -> *mut T { self.this as *mut T } @@ -62,12 +122,26 @@ impl<'file_ref, T> ThisMut<'file_ref, T> for RefThisMut<'file_ref, T> { } } +/// A [This] instance that is "owned" by the caller. +/// +/// "Owned" in this context only really means that the object is not a rust reference. +/// In practice, all "owned" taglib objects are actually shared references, and are +/// thus tied to the lifetime of the `'file_ref` parameter. pub struct OwnedThis<'file_ref, T : UniquePtrTarget> { _data: PhantomData<&'file_ref ()>, this: UniquePtr, } impl<'file_ref, T : UniquePtrTarget> OwnedThis<'file_ref, T> { + /// Create a new [OwnedThis] from a [UniquePtr]. + /// + /// This is safe to call assuming the contract of [This] is upheld. Since this + /// contract cannot be enforced by the Rust compiler, it is the caller's + /// responsibility to ensure that the `UniquePtr` is valid for the lifetime of + /// the `'file_ref` parameter. More or less, if it comes from the TagLib FFI + /// interface, it is safe to use this. + /// + /// This will return `None` if the `UniquePtr` is `null`. pub unsafe fn new(this: UniquePtr) -> Option { if !this.is_null() { Some(Self {