diff --git a/.github/workflows/libloading.yml b/.github/workflows/libloading.yml index 83b7b94..2ba5d13 100644 --- a/.github/workflows/libloading.yml +++ b/.github/workflows/libloading.yml @@ -14,7 +14,7 @@ jobs: strategy: fail-fast: false matrix: - rust_toolchain: [nightly, stable, 1.36.0] + rust_toolchain: [nightly, stable, 1.40.0] os: [ubuntu-latest, windows-latest, macOS-latest] timeout-minutes: 20 steps: diff --git a/src/changelog.rs b/src/changelog.rs index 13dcec3..06c8672 100644 --- a/src/changelog.rs +++ b/src/changelog.rs @@ -4,10 +4,15 @@ /// /// * Introduced a new method [`os::unix::Library::get_singlethreaded`]; /// * Added (untested) support for building when targetting Redox and Fuchsia; +/// * The APIs exposed by this library no longer panic and instead return an `Err` when it used +/// to panic. /// /// ## Breaking changes /// -/// * Minimum required (stable) version of Rust to build this library is now 1.36.0; +/// * Minimum required (stable) version of Rust to build this library is now 1.40.0; +/// * This crate now implements a custom [`Error`] type and all APIs now return this type rather +/// than returning the `std::io::Error`; +/// * `libloading::Result` has been removed; /// * Removed the dependency on the C compiler to build this library on UNIX-like platforms. /// `libloading` used to utilize a snippet written in C to work-around the unlikely possibility /// of the target having a thread-unsafe implementation of the `dlerror` function. The effect of @@ -21,6 +26,7 @@ /// /// [`Library::get`]: crate::Library::get /// [`os::unix::Library::get_singlethreaded`]: crate::os::unix::Library::get_singlethreaded +/// [`Error`]: crate::Error pub mod r0_6_0 {} diff --git a/src/error.rs b/src/error.rs new file mode 100644 index 0000000..dcbe2b1 --- /dev/null +++ b/src/error.rs @@ -0,0 +1,114 @@ +use std::ffi::CString; + +pub struct DlDescription(pub(crate) CString); + +impl std::fmt::Debug for DlDescription { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + std::fmt::Debug::fmt(&self.0, f) + } +} + +pub struct WindowsError(pub(crate) std::io::Error); + +impl std::fmt::Debug for WindowsError { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + std::fmt::Debug::fmt(&self.0, f) + } +} + +#[derive(Debug)] +#[non_exhaustive] +pub enum Error { + /// The `dlopen` call failed. + DlOpen { desc: DlDescription }, + /// The `dlopen` call failed and system did not report an error. + DlOpenUnknown, + /// The `dlsym` call failed. + DlSym { desc: DlDescription }, + /// The `dlsym` call failed and system did not report an error. + DlSymUnknown, + /// The `dlclose` call failed. + DlClose { desc: DlDescription }, + /// The `dlclose` call failed and system did not report an error. + DlCloseUnknown, + /// The `LoadLibraryW` call failed. + LoadLibraryW { source: WindowsError }, + /// The `LoadLibraryW` call failed and system did not report an error. + LoadLibraryWUnknown, + /// The `GetProcAddress` call failed. + GetProcAddress { source: WindowsError }, + /// The `GetProcAddressUnknown` call failed and system did not report an error. + GetProcAddressUnknown, + /// The `FreeLibrary` call failed. + FreeLibrary { source: WindowsError }, + /// The `FreeLibrary` call failed and system did not report an error. + FreeLibraryUnknown, + /// The requested type cannot possibly work. + IncompatibleSize, + /// Could not create a new CString. + CreateCString { source: std::ffi::NulError }, + /// Could not create a new CString from bytes with trailing null. + CreateCStringWithTrailing { source: std::ffi::FromBytesWithNulError }, +} + +impl std::error::Error for Error { + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { + use Error::*; + match *self { + CreateCString { ref source } => Some(source), + CreateCStringWithTrailing { ref source } => Some(source), + LoadLibraryW { ref source } => Some(&source.0), + GetProcAddress { ref source } => Some(&source.0), + FreeLibrary { ref source } => Some(&source.0), + _ => None, + } + } +} + +impl std::fmt::Display for Error { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + use Error::*; + match *self { + DlOpen { ref desc } => write!(f, "{}", desc.0.to_string_lossy()), + DlOpenUnknown => write!(f, "dlopen failed, but system did not report the error"), + DlSym { ref desc } => write!(f, "{}", desc.0.to_string_lossy()), + DlSymUnknown => write!(f, "dlsym failed, but system did not report the error"), + DlClose { ref desc } => write!(f, "{}", desc.0.to_string_lossy()), + DlCloseUnknown => write!(f, "dlclose failed, but system did not report the error"), + LoadLibraryW { .. } => write!(f, "LoadLibraryW failed"), + LoadLibraryWUnknown => + write!(f, "LoadLibraryW failed, but system did not report the error"), + GetProcAddress { .. } => write!(f, "GetProcAddress failed"), + GetProcAddressUnknown => + write!(f, "GetProcAddress failed, but system did not report the error"), + FreeLibrary { .. } => write!(f, "FreeLibrary failed"), + FreeLibraryUnknown => + write!(f, "FreeLibrary failed, but system did not report the error"), + CreateCString { .. } => write!(f, "could not create a C string from bytes"), + CreateCStringWithTrailing { .. } => + write!(f, "could not create a C string from bytes with trailing null"), + IncompatibleSize => write!(f, "requested type cannot possibly work"), + } + } +} + +#[cfg(test)] +mod tests { + #[test] + fn error_send() { + fn assert_send() {} + assert_send::(); + } + + #[test] + fn error_sync() { + fn assert_sync() {} + assert_sync::(); + } + + #[test] + fn error_display() { + fn assert_display() {} + assert_display::(); + } +} diff --git a/src/lib.rs b/src/lib.rs index b63fb97..fad7d63 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -25,7 +25,7 @@ //! ```no_run //! extern crate libloading as lib; //! -//! fn call_dynamic() -> lib::Result { +//! fn call_dynamic() -> Result> { //! let lib = lib::Library::new("/path/to/liblibrary.so")?; //! unsafe { //! let func: lib::Symbol u32> = lib.get(b"my_func")?; @@ -45,12 +45,12 @@ use std::marker; use self::os::unix as imp; #[cfg(windows)] use self::os::windows as imp; +pub use self::error::Error; pub mod os; pub mod changelog; mod util; - -pub type Result = ::std::io::Result; +mod error; /// A loaded dynamic library. pub struct Library(imp::Library); @@ -114,7 +114,7 @@ impl Library { /// let _ = Library::new("../awesome.module").unwrap(); /// let _ = Library::new("libsomelib.so.1").unwrap(); /// ``` - pub fn new>(filename: P) -> Result { + pub fn new>(filename: P) -> Result { imp::Library::new(filename).map(From::from) } @@ -174,9 +174,21 @@ impl Library { /// **awesome_variable = 42.0; /// }; /// ``` - pub unsafe fn get<'lib, T>(&'lib self, symbol: &[u8]) -> Result> { + pub unsafe fn get<'lib, T>(&'lib self, symbol: &[u8]) -> Result, Error> { self.0.get(symbol).map(|from| Symbol::from_raw(from, self)) } + + /// Unload the library. + /// + /// This method might be a no-op, depending on the flags with which the `Library` was opened, + /// what library was opened or other platform specifics. + /// + /// You only need to call this if you are interested in handling any errors that may arise when + /// library is unloaded. Otherwise the implementation of `Drop` for `Library` will close the + /// library and ignore the errors were they arise. + pub fn close(self) -> Result<(), Error> { + self.0.close() + } } impl fmt::Debug for Library { diff --git a/src/os/unix/mod.rs b/src/os/unix/mod.rs index b3eb544..29d2003 100644 --- a/src/os/unix/mod.rs +++ b/src/os/unix/mod.rs @@ -1,7 +1,7 @@ use util::{ensure_compatible_types, cstr_cow_from_bytes}; use std::ffi::{CStr, OsStr}; -use std::{fmt, io, marker, mem, ptr}; +use std::{fmt, marker, mem, ptr}; use std::os::raw; use std::os::unix::ffi::OsStrExt; @@ -14,7 +14,8 @@ use std::os::unix::ffi::OsStrExt; // In practice (as of 2020-04-01) most of the widely used targets use a thread-local for error // state and have been doing so for a long time. Regardless the comments in this function shall // remain as a documentation for the future generations. -fn with_dlerror(closure: F) -> Result> +fn with_dlerror(wrap: fn(crate::error::DlDescription) -> crate::Error, closure: F) +-> Result> where F: FnOnce() -> Option { // We used to guard all uses of dl* functions with our own mutex. This made them safe to use in // MT programs provided the only way a program used dl* was via this library. However, it also @@ -52,8 +53,8 @@ where F: FnOnce() -> Option { // ownership over the message? // TODO: should do locale-aware conversion here. OTOH Rust doesn’t seem to work well in // any system that uses non-utf8 locale, so I doubt there’s a problem here. - let message = CStr::from_ptr(error).to_string_lossy().into_owned(); - Some(io::Error::new(io::ErrorKind::Other, message)) + let message = CStr::from_ptr(error).into(); + Some(wrap(crate::error::DlDescription(message))) // Since we do a copy of the error string above, maybe we should call dlerror again to // let libdl know it may free its copy of the string now? } @@ -91,7 +92,7 @@ impl Library { /// /// Corresponds to `dlopen(filename, RTLD_NOW)`. #[inline] - pub fn new>(filename: P) -> ::Result { + pub fn new>(filename: P) -> Result { Library::open(Some(filename), RTLD_NOW) } @@ -103,7 +104,7 @@ impl Library { /// Corresponds to `dlopen(NULL, RTLD_NOW)`. #[inline] pub fn this() -> Library { - Library::open(None::<&OsStr>, RTLD_NOW).unwrap() + Library::open(None::<&OsStr>, RTLD_NOW).expect("this should never fail") } /// Find and load a shared library (module). @@ -114,13 +115,13 @@ impl Library { /// If the `filename` is None, null pointer is passed to `dlopen`. /// /// Corresponds to `dlopen(filename, flags)`. - pub fn open

(filename: Option

, flags: raw::c_int) -> ::Result + pub fn open

(filename: Option

, flags: raw::c_int) -> Result where P: AsRef { let filename = match filename { None => None, Some(ref f) => Some(cstr_cow_from_bytes(f.as_ref().as_bytes())?), }; - with_dlerror(move || { + with_dlerror(|desc| crate::Error::DlOpen { desc }, move || { let result = unsafe { let r = dlopen(match filename { None => ptr::null(), @@ -137,18 +138,13 @@ impl Library { handle: result }) } - }).map_err(|e| e.unwrap_or_else(|| - io::Error::new( - io::ErrorKind::Other, - "dlopen returned a null and dlerror reported no error" - ) - )) + }).map_err(|e| e.unwrap_or(crate::Error::DlOpenUnknown)) } - unsafe fn get_impl(&self, symbol: &[u8], on_null: F) -> ::Result> - where F: FnOnce() -> ::Result> + unsafe fn get_impl(&self, symbol: &[u8], on_null: F) -> Result, crate::Error> + where F: FnOnce() -> Result, crate::Error> { - ensure_compatible_types::(); + ensure_compatible_types::()?; let symbol = cstr_cow_from_bytes(symbol)?; // `dlsym` may return nullptr in two cases: when a symbol genuinely points to a null // pointer or the symbol cannot be found. In order to detect this case a double dlerror @@ -156,7 +152,7 @@ impl Library { // // We try to leave as little space as possible for this to occur, but we can’t exactly // fully prevent it. - match with_dlerror(|| { + match with_dlerror(|desc| crate::Error::DlSym { desc }, || { dlerror(); let symbol = dlsym(self.handle, symbol.as_ptr()); if symbol.is_null() { @@ -202,17 +198,12 @@ impl Library { /// pointer without it being an error. If loading a null pointer is something you care about, /// consider using the [`Library::get_singlethreaded`] call. #[inline(always)] - pub unsafe fn get(&self, symbol: &[u8]) -> ::Result> { + pub unsafe fn get(&self, symbol: &[u8]) -> Result, crate::Error> { #[cfg(mtsafe_dlerror)] { return self.get_singlethreaded(symbol); } #[cfg(not(mtsafe_dlerror))] { - return self.get_impl(symbol, || { - Err(io::Error::new( - io::ErrorKind::Other, - "dlsym returned a null and MT-unsafe dlerror reported no error" - )) - }); + return self.get_impl(symbol, || Err(crate::Error::DlSymUnknown)); } } @@ -239,7 +230,7 @@ impl Library { /// OS X uses some sort of lazy initialization scheme, which makes loading TLS variables /// impossible. Using a TLS variable loaded this way on OS X is undefined behaviour. #[inline(always)] - pub unsafe fn get_singlethreaded(&self, symbol: &[u8]) -> ::Result> { + pub unsafe fn get_singlethreaded(&self, symbol: &[u8]) -> Result, crate::Error> { self.get_impl(symbol, || Ok(Symbol { pointer: ptr::null_mut(), pd: marker::PhantomData @@ -268,15 +259,33 @@ impl Library { handle: handle } } + + /// Unload the library. + /// + /// This method might be a no-op, depending on the flags with which the `Library` was opened, + /// what library was opened or other platform specifics. + /// + /// You only need to call this if you are interested in handling any errors that may arise when + /// library is unloaded. Otherwise the implementation of `Drop` for `Library` will close the + /// library and ignore the errors were they arise. + pub fn close(self) -> Result<(), crate::Error> { + let result = with_dlerror(|desc| crate::Error::DlClose { desc }, || { + if unsafe { dlclose(self.handle) } == 0 { + Some(()) + } else { + None + } + }).map_err(|e| e.unwrap_or(crate::Error::DlCloseUnknown)); + std::mem::forget(self); + result + } } impl Drop for Library { fn drop(&mut self) { - with_dlerror(|| if unsafe { dlclose(self.handle) } == 0 { - Some(()) - } else { - None - }).unwrap(); + unsafe { + dlclose(self.handle); + } } } @@ -382,7 +391,10 @@ struct DlInfo { dli_saddr: *mut raw::c_void } -#[test] -fn this() { - Library::this(); +#[cfg(test)] +mod tests { + #[test] + fn this() { + super::Library::this(); + } } diff --git a/src/os/windows/mod.rs b/src/os/windows/mod.rs index 6d0126b..646700e 100644 --- a/src/os/windows/mod.rs +++ b/src/os/windows/mod.rs @@ -11,7 +11,6 @@ use std::{fmt, io, marker, mem, ptr}; use std::os::windows::ffi::{OsStrExt, OsStringExt}; use std::sync::atomic::{AtomicBool, ATOMIC_BOOL_INIT, Ordering}; - /// A platform-specific equivalent of the cross-platform `Library`. pub struct Library(HMODULE); @@ -36,11 +35,11 @@ impl Library { /// /// Corresponds to `LoadLibraryW(filename)`. #[inline] - pub fn new>(filename: P) -> ::Result { + pub fn new>(filename: P) -> Result { let wide_filename: Vec = filename.as_ref().encode_wide().chain(Some(0)).collect(); let _guard = ErrorModeGuard::new(); - let ret = with_get_last_error(|| { + let ret = with_get_last_error(|source| crate::Error::LoadLibraryW { source }, || { // Make sure no winapi calls as a result of drop happen inside this closure, because // otherwise that might change the return value of the GetLastError. let handle = unsafe { libloaderapi::LoadLibraryW(wide_filename.as_ptr()) }; @@ -49,10 +48,7 @@ impl Library { } else { Some(Library(handle)) } - }).map_err(|e| e.unwrap_or_else(|| - panic!("LoadLibraryW failed but GetLastError did not report the error") - )); - + }).map_err(|e| e.unwrap_or(crate::Error::LoadLibraryWUnknown)); drop(wide_filename); // Drop wide_filename here to ensure it doesn’t get moved and dropped // inside the closure by mistake. See comment inside the closure. ret @@ -71,10 +67,10 @@ impl Library { /// This function does not validate the type `T`. It is up to the user of this function to /// ensure that the loaded symbol is in fact a `T`. Using a value with a wrong type has no /// definied behaviour. - pub unsafe fn get(&self, symbol: &[u8]) -> ::Result> { - ensure_compatible_types::(); + pub unsafe fn get(&self, symbol: &[u8]) -> Result, crate::Error> { + ensure_compatible_types::()?; let symbol = cstr_cow_from_bytes(symbol)?; - with_get_last_error(|| { + with_get_last_error(|source| crate::Error::GetProcAddress { source }, || { let symbol = libloaderapi::GetProcAddress(self.0, symbol.as_ptr()); if symbol.is_null() { None @@ -84,9 +80,7 @@ impl Library { pd: marker::PhantomData }) } - }).map_err(|e| e.unwrap_or_else(|| - panic!("GetProcAddress failed but GetLastError did not report the error") - )) + }).map_err(|e| e.unwrap_or(crate::Error::GetProcAddressUnknown)) } /// Get a pointer to function or static variable by ordinal number. @@ -95,9 +89,9 @@ impl Library { /// /// Pointer to a value of arbitrary type is returned. Using a value with wrong type is /// undefined. - pub unsafe fn get_ordinal(&self, ordinal: WORD) -> ::Result> { - ensure_compatible_types::(); - with_get_last_error(|| { + pub unsafe fn get_ordinal(&self, ordinal: WORD) -> Result, crate::Error> { + ensure_compatible_types::()?; + with_get_last_error(|source| crate::Error::GetProcAddress { source }, || { let ordinal = ordinal as usize as *mut _; let symbol = libloaderapi::GetProcAddress(self.0, ordinal); if symbol.is_null() { @@ -108,9 +102,7 @@ impl Library { pd: marker::PhantomData }) } - }).map_err(|e| e.unwrap_or_else(|| - panic!("GetProcAddress failed but GetLastError did not report the error") - )) + }).map_err(|e| e.unwrap_or(crate::Error::GetProcAddressUnknown)) } /// Convert the `Library` to a raw handle. @@ -129,17 +121,22 @@ impl Library { pub unsafe fn from_raw(handle: HMODULE) -> Library { Library(handle) } -} -impl Drop for Library { - fn drop(&mut self) { - with_get_last_error(|| { + /// Unload the library. + pub fn close(self) -> Result<(), crate::Error> { + with_get_last_error(|source| crate::Error::FreeLibrary { source }, || { if unsafe { libloaderapi::FreeLibrary(self.0) == 0 } { None } else { Some(()) } - }).unwrap() + }).map_err(|e| e.unwrap_or(crate::Error::FreeLibraryUnknown)) + } +} + +impl Drop for Library { + fn drop(&mut self) { + unsafe { libloaderapi::FreeLibrary(self.0); } } } @@ -277,38 +274,44 @@ impl Drop for ErrorModeGuard { } } -fn with_get_last_error(closure: F) -> Result> +fn with_get_last_error(wrap: fn(crate::error::WindowsError) -> crate::Error, closure: F) +-> Result> where F: FnOnce() -> Option { closure().ok_or_else(|| { let error = unsafe { errhandlingapi::GetLastError() }; if error == 0 { None } else { - Some(io::Error::from_raw_os_error(error as i32)) + Some(wrap(crate::error::WindowsError(io::Error::from_raw_os_error(error as i32)))) } }) } -#[test] -fn works_getlasterror() { - let lib = Library::new("kernel32.dll").unwrap(); - let gle: Symbol DWORD> = unsafe { - lib.get(b"GetLastError").unwrap() - }; - unsafe { - errhandlingapi::SetLastError(42); - assert_eq!(errhandlingapi::GetLastError(), gle()) - } -} +#[cfg(test)] +mod tests { + use super::*; -#[test] -fn works_getlasterror0() { - let lib = Library::new("kernel32.dll").unwrap(); - let gle: Symbol DWORD> = unsafe { - lib.get(b"GetLastError\0").unwrap() - }; - unsafe { - errhandlingapi::SetLastError(42); - assert_eq!(errhandlingapi::GetLastError(), gle()) + #[test] + fn works_getlasterror() { + let lib = Library::new("kernel32.dll").unwrap(); + let gle: Symbol DWORD> = unsafe { + lib.get(b"GetLastError").unwrap() + }; + unsafe { + errhandlingapi::SetLastError(42); + assert_eq!(errhandlingapi::GetLastError(), gle()) + } + } + + #[test] + fn works_getlasterror0() { + let lib = Library::new("kernel32.dll").unwrap(); + let gle: Symbol DWORD> = unsafe { + lib.get(b"GetLastError\0").unwrap() + }; + unsafe { + errhandlingapi::SetLastError(42); + assert_eq!(errhandlingapi::GetLastError(), gle()) + } } } diff --git a/src/util.rs b/src/util.rs index 5fe8164..e5108c2 100644 --- a/src/util.rs +++ b/src/util.rs @@ -1,61 +1,31 @@ -use std::ffi::{CStr, CString, NulError, FromBytesWithNulError}; +use std::ffi::{CStr, CString}; use std::borrow::Cow; use std::os::raw; -#[derive(Debug)] -pub struct NullError; - -impl From for NullError { - fn from(_: NulError) -> NullError { - NullError - } -} - -impl From for NullError { - fn from(_: FromBytesWithNulError) -> NullError { - NullError - } -} - -impl From for ::std::io::Error { - fn from(e: NullError) -> ::std::io::Error { - ::std::io::Error::new(::std::io::ErrorKind::Other, format!("{}", e)) - } -} - -impl ::std::error::Error for NullError { - fn description(&self) -> &str { "non-final null byte found" } -} - -impl ::std::fmt::Display for NullError { - fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result { - write!(f, "non-final null byte found") - } -} +use crate::Error; /// Checks for last byte and avoids allocating if it is zero. /// /// Non-last null bytes still result in an error. -pub fn cstr_cow_from_bytes<'a>(slice: &'a [u8]) -> Result, NullError> { +pub(crate) fn cstr_cow_from_bytes<'a>(slice: &'a [u8]) -> Result, Error> { static ZERO: raw::c_char = 0; Ok(match slice.last() { // Slice out of 0 elements None => unsafe { Cow::Borrowed(CStr::from_ptr(&ZERO)) }, // Slice with trailing 0 - Some(&0) => Cow::Borrowed(CStr::from_bytes_with_nul(slice)?), + Some(&0) => Cow::Borrowed(CStr::from_bytes_with_nul(slice) + .map_err(|source| Error::CreateCStringWithTrailing { source })?), // Slice with no trailing 0 - Some(_) => Cow::Owned(CString::new(slice)?), + Some(_) => Cow::Owned(CString::new(slice) + .map_err(|source| Error::CreateCString { source })?), }) } #[inline] -pub fn ensure_compatible_types() { - #[cold] - #[inline(never)] - fn dopanic() { - panic!("value of requested type cannot be dynamically loaded"); - } +pub(crate) fn ensure_compatible_types() -> Result<(), Error> { if ::std::mem::size_of::() != ::std::mem::size_of::() { - dopanic() + Err(Error::IncompatibleSize) + } else { + Ok(()) } } diff --git a/tests/functions.rs b/tests/functions.rs index c9bc067..dece58c 100644 --- a/tests/functions.rs +++ b/tests/functions.rs @@ -4,7 +4,7 @@ use libloading::{Symbol, Library}; const LIBPATH: &'static str = concat!(env!("OUT_DIR"), "/libtest_helpers.dll"); fn make_helpers() { - static ONCE: ::std::sync::Once = ::std::sync::ONCE_INIT; + static ONCE: ::std::sync::Once = ::std::sync::Once::new(); ONCE.call_once(|| { let mut outpath = String::from(if let Some(od) = option_env!("OUT_DIR") { od } else { return }); let rustc = option_env!("RUSTC").unwrap_or_else(|| { "rustc".into() }); @@ -85,25 +85,29 @@ fn interior_null_fails() { } #[test] -#[should_panic] fn test_incompatible_type() { make_helpers(); let lib = Library::new(LIBPATH).unwrap(); unsafe { - let _ = lib.get::<()>(b"test_identity_u32\0"); + assert!(match lib.get::<()>(b"test_identity_u32\0") { + Err(libloading::Error::IncompatibleSize) => true, + _ => false, + }) } } #[test] -#[should_panic] fn test_incompatible_type_named_fn() { make_helpers(); - unsafe fn get<'a, T>(l: &'a Library, _: T) -> libloading::Result> { + unsafe fn get<'a, T>(l: &'a Library, _: T) -> Result, libloading::Error> { l.get::(b"test_identity_u32\0") } let lib = Library::new(LIBPATH).unwrap(); unsafe { - let _ = get(&lib, test_incompatible_type_named_fn); + assert!(match get(&lib, test_incompatible_type_named_fn) { + Err(libloading::Error::IncompatibleSize) => true, + _ => false, + }) } }