use std::path::Path;
use block2::RcBlock;
use objc2::{
Message, extern_class, extern_conformance, extern_methods, extern_protocol, msg_send,
rc::{Allocated, Retained},
runtime::ProtocolObject,
};
use objc2_foundation::{CopyingHelper, NSArray, NSCopying, NSError, NSObject, NSObjectProtocol, NSString};
use crate::{CallbackBlock, *};
pub struct MTLNewLibraryCompletionHandler(RcBlock<dyn Fn(*mut ProtocolObject<dyn MTLLibrary>, *mut NSError)>);
impl MTLNewLibraryCompletionHandler {
pub fn new<F>(handler: F) -> Self
where
F: Fn(Option<Retained<ProtocolObject<dyn MTLLibrary>>>, Option<MetalError>) + 'static,
{
Self(RcBlock::new(move |library_ptr: *mut ProtocolObject<dyn MTLLibrary>, error: *mut NSError| {
let library = unsafe { Retained::retain(library_ptr) };
let error = unsafe { MetalError::from_unretained(error) };
handler(library, error);
}))
}
}
pub struct MTLNewDynamicLibraryCompletionHandler(
RcBlock<dyn Fn(*mut ProtocolObject<dyn MTLDynamicLibrary>, *mut NSError)>,
);
impl MTLNewDynamicLibraryCompletionHandler {
pub fn new<F>(handler: F) -> Self
where
F: Fn(Option<Retained<ProtocolObject<dyn MTLDynamicLibrary>>>, Option<MetalError>) + 'static,
{
Self(RcBlock::new(move |library_ptr: *mut ProtocolObject<dyn MTLDynamicLibrary>, error: *mut NSError| {
let library = unsafe { Retained::retain(library_ptr) };
let error = unsafe { MetalError::from_unretained(error) };
handler(library, error);
}))
}
}
pub struct MTLNewComputePipelineStateCompletionHandler(
RcBlock<dyn Fn(*mut ProtocolObject<dyn MTLComputePipelineState>, *mut NSError)>,
);
impl MTLNewComputePipelineStateCompletionHandler {
pub fn new<F>(handler: F) -> Self
where
F: Fn(Option<Retained<ProtocolObject<dyn MTLComputePipelineState>>>, Option<MetalError>) + 'static,
{
Self(RcBlock::new(
move |pipeline_ptr: *mut ProtocolObject<dyn MTLComputePipelineState>, error: *mut NSError| {
let pipeline = unsafe { Retained::retain(pipeline_ptr) };
let error = unsafe { MetalError::from_unretained(error) };
handler(pipeline, error);
},
))
}
}
extern_class!(
#[unsafe(super(NSObject))]
#[derive(Debug, PartialEq, Eq, Hash)]
pub struct MTL4CompilerDescriptor;
);
extern_conformance!(
unsafe impl NSCopying for MTL4CompilerDescriptor {}
);
unsafe impl CopyingHelper for MTL4CompilerDescriptor {
type Result = Self;
}
extern_conformance!(
unsafe impl NSObjectProtocol for MTL4CompilerDescriptor {}
);
impl MTL4CompilerDescriptor {
extern_methods!(
#[unsafe(method(pipelineDataSetSerializer))]
#[unsafe(method_family = none)]
pub fn pipeline_data_set_serializer(
&self
) -> Option<Retained<ProtocolObject<dyn MTL4PipelineDataSetSerializer>>>;
#[unsafe(method(setPipelineDataSetSerializer:))]
#[unsafe(method_family = none)]
pub fn set_pipeline_data_set_serializer(
&self,
pipeline_data_set_serializer: Option<&ProtocolObject<dyn MTL4PipelineDataSetSerializer>>,
);
);
pub fn label(&self) -> Option<String> {
let label: Option<Retained<NSString>> = unsafe { msg_send![self, label] };
label.map(|label| label.to_string())
}
pub fn set_label(
&self,
label: Option<&str>,
) {
unsafe {
let _: () = msg_send![self, setLabel: label.map(NSString::from_str).as_deref()];
}
}
}
impl MTL4CompilerDescriptor {
extern_methods!(
#[unsafe(method(init))]
#[unsafe(method_family = init)]
pub fn init(this: Allocated<Self>) -> Retained<Self>;
#[unsafe(method(new))]
#[unsafe(method_family = new)]
pub fn new() -> Retained<Self>;
);
}
extern_class!(
#[unsafe(super(NSObject))]
#[derive(Debug, PartialEq, Eq, Hash)]
pub struct MTL4CompilerTaskOptions;
);
extern_conformance!(
unsafe impl NSCopying for MTL4CompilerTaskOptions {}
);
unsafe impl CopyingHelper for MTL4CompilerTaskOptions {
type Result = Self;
}
extern_conformance!(
unsafe impl NSObjectProtocol for MTL4CompilerTaskOptions {}
);
impl MTL4CompilerTaskOptions {
pub fn lookup_archives(&self) -> Option<Box<[Retained<ProtocolObject<dyn MTL4Archive>>]>> {
let archives: Option<Retained<NSArray<ProtocolObject<dyn MTL4Archive>>>> =
unsafe { msg_send![self, lookupArchives] };
archives.map(|archives| archives.to_vec().into_boxed_slice())
}
pub fn set_lookup_archives(
&self,
lookup_archives: Option<&[&ProtocolObject<dyn MTL4Archive>]>,
) {
let lookup_archives = lookup_archives.map(NSArray::from_slice);
unsafe {
let _: () = msg_send![self, setLookupArchives: lookup_archives.as_deref()];
}
}
}
impl MTL4CompilerTaskOptions {
extern_methods!(
#[unsafe(method(init))]
#[unsafe(method_family = init)]
pub fn init(this: Allocated<Self>) -> Retained<Self>;
#[unsafe(method(new))]
#[unsafe(method_family = new)]
pub fn new() -> Retained<Self>;
);
}
pub struct MTL4NewBinaryFunctionCompletionHandler(
RcBlock<dyn Fn(*mut ProtocolObject<dyn MTL4BinaryFunction>, *mut NSError)>,
);
impl MTL4NewBinaryFunctionCompletionHandler {
pub fn new<F>(handler: F) -> Self
where
F: Fn(Option<Retained<ProtocolObject<dyn MTL4BinaryFunction>>>, Option<MetalError>) + Send + Sync + 'static,
{
Self(RcBlock::new(move |binary_fn_ptr: *mut ProtocolObject<dyn MTL4BinaryFunction>, error: *mut NSError| {
let binary_fn = unsafe { Retained::retain(binary_fn_ptr) };
let error = unsafe { MetalError::from_unretained(error) };
handler(binary_fn, error);
}))
}
}
pub struct MTL4NewMachineLearningPipelineStateCompletionHandler(
RcBlock<dyn Fn(*mut ProtocolObject<dyn MTL4MachineLearningPipelineState>, *mut NSError)>,
);
impl MTL4NewMachineLearningPipelineStateCompletionHandler {
pub fn new<F>(handler: F) -> Self
where
F: Fn(Option<Retained<ProtocolObject<dyn MTL4MachineLearningPipelineState>>>, Option<MetalError>)
+ Send
+ Sync
+ 'static,
{
Self(RcBlock::new(
move |pipeline_ptr: *mut ProtocolObject<dyn MTL4MachineLearningPipelineState>, error: *mut NSError| {
let pipeline = unsafe { Retained::retain(pipeline_ptr) };
let error = unsafe { MetalError::from_unretained(error) };
handler(pipeline, error);
},
))
}
}
extern_protocol!(
#[expect(
clippy::missing_safety_doc,
reason = "extern_protocol does not attach this safety section to its generated unsafe trait"
)]
pub unsafe trait MTL4Compiler: NSObjectProtocol + Send + Sync {
#[unsafe(method(device))]
#[unsafe(method_family = none)]
fn device(&self) -> Retained<ProtocolObject<dyn MTLDevice>>;
#[unsafe(method(pipelineDataSetSerializer))]
#[unsafe(method_family = none)]
fn pipeline_data_set_serializer(&self) -> Option<Retained<ProtocolObject<dyn MTL4PipelineDataSetSerializer>>>;
}
);
pub trait MTL4CompilerExt: MTL4Compiler + Message {
fn label(&self) -> Option<String>
where
Self: Sized,
{
let label: Option<Retained<NSString>> = unsafe { msg_send![self, label] };
label.map(|label| label.to_string())
}
fn new_library_with_descriptor_error(
&self,
descriptor: &MTL4LibraryDescriptor,
) -> Result<Retained<ProtocolObject<dyn MTLLibrary>>, MetalError>
where
Self: Sized,
{
unsafe { msg_send![self, newLibraryWithDescriptor: descriptor, error: _] }.map_err(MetalError::from_nserror)
}
fn new_dynamic_library_error(
&self,
library: &ProtocolObject<dyn MTLLibrary>,
) -> Result<Retained<ProtocolObject<dyn MTLDynamicLibrary>>, MetalError>
where
Self: Sized,
{
unsafe { msg_send![self, newDynamicLibrary: library, error: _] }.map_err(MetalError::from_nserror)
}
fn new_compute_pipeline_state_with_descriptor_compiler_task_options_error(
&self,
descriptor: &MTL4ComputePipelineDescriptor,
compiler_task_options: Option<&MTL4CompilerTaskOptions>,
) -> Result<Retained<ProtocolObject<dyn MTLComputePipelineState>>, MetalError>
where
Self: Sized,
{
unsafe {
msg_send![
self,
newComputePipelineStateWithDescriptor: descriptor,
compilerTaskOptions: compiler_task_options,
error: _
]
}
.map_err(MetalError::from_nserror)
}
fn new_compute_pipeline_state_with_descriptor_dynamic_linking_descriptor_compiler_task_options_error(
&self,
descriptor: &MTL4ComputePipelineDescriptor,
dynamic_linking_descriptor: Option<&MTL4PipelineStageDynamicLinkingDescriptor>,
compiler_task_options: Option<&MTL4CompilerTaskOptions>,
) -> Result<Retained<ProtocolObject<dyn MTLComputePipelineState>>, MetalError>
where
Self: Sized,
{
unsafe {
msg_send![
self,
newComputePipelineStateWithDescriptor: descriptor,
dynamicLinkingDescriptor: dynamic_linking_descriptor,
compilerTaskOptions: compiler_task_options,
error: _
]
}
.map_err(MetalError::from_nserror)
}
fn new_render_pipeline_state_with_descriptor_compiler_task_options_error(
&self,
descriptor: &MTL4PipelineDescriptor,
compiler_task_options: Option<&MTL4CompilerTaskOptions>,
) -> Result<Retained<ProtocolObject<dyn MTLRenderPipelineState>>, MetalError>
where
Self: Sized,
{
unsafe {
msg_send![
self,
newRenderPipelineStateWithDescriptor: descriptor,
compilerTaskOptions: compiler_task_options,
error: _
]
}
.map_err(MetalError::from_nserror)
}
fn new_render_pipeline_state_with_descriptor_dynamic_linking_descriptor_compiler_task_options_error(
&self,
descriptor: &MTL4PipelineDescriptor,
dynamic_linking_descriptor: Option<&MTL4RenderPipelineDynamicLinkingDescriptor>,
compiler_task_options: Option<&MTL4CompilerTaskOptions>,
) -> Result<Retained<ProtocolObject<dyn MTLRenderPipelineState>>, MetalError>
where
Self: Sized,
{
unsafe {
msg_send![
self,
newRenderPipelineStateWithDescriptor: descriptor,
dynamicLinkingDescriptor: dynamic_linking_descriptor,
compilerTaskOptions: compiler_task_options,
error: _
]
}
.map_err(MetalError::from_nserror)
}
fn new_render_pipeline_state_by_specialization_with_descriptor_pipeline_error(
&self,
descriptor: &MTL4PipelineDescriptor,
pipeline: &ProtocolObject<dyn MTLRenderPipelineState>,
) -> Result<Retained<ProtocolObject<dyn MTLRenderPipelineState>>, MetalError>
where
Self: Sized,
{
unsafe {
msg_send![
self,
newRenderPipelineStateBySpecializationWithDescriptor: descriptor,
pipeline: pipeline,
error: _
]
}
.map_err(MetalError::from_nserror)
}
fn new_binary_function_with_descriptor_compiler_task_options_error(
&self,
descriptor: &MTL4BinaryFunctionDescriptor,
compiler_task_options: Option<&MTL4CompilerTaskOptions>,
) -> Result<Retained<ProtocolObject<dyn MTL4BinaryFunction>>, MetalError>
where
Self: Sized,
{
unsafe {
msg_send![
self,
newBinaryFunctionWithDescriptor: descriptor,
compilerTaskOptions: compiler_task_options,
error: _
]
}
.map_err(MetalError::from_nserror)
}
fn new_machine_learning_pipeline_state_with_descriptor_error(
&self,
descriptor: &MTL4MachineLearningPipelineDescriptor,
) -> Result<Retained<ProtocolObject<dyn MTL4MachineLearningPipelineState>>, MetalError>
where
Self: Sized,
{
unsafe { msg_send![self, newMachineLearningPipelineStateWithDescriptor: descriptor, error: _] }
.map_err(MetalError::from_nserror)
}
fn new_dynamic_library_with_path(
&self,
path: &Path,
) -> Result<Retained<ProtocolObject<dyn MTLDynamicLibrary>>, MetalError>
where
Self: Sized,
{
let url = crate::util::file_url(path, "newDynamicLibraryWithURL:error:")?;
unsafe { msg_send![self, newDynamicLibraryWithURL: &*url, error: _] }.map_err(MetalError::from_nserror)
}
fn new_library_with_descriptor_completion_handler(
&self,
descriptor: &MTL4LibraryDescriptor,
completion_handler: MTLNewLibraryCompletionHandler,
) -> Retained<ProtocolObject<dyn MTL4CompilerTask>>
where
Self: Sized,
{
unsafe {
msg_send![
self,
newLibraryWithDescriptor: descriptor,
completionHandler: &*completion_handler.0
]
}
}
fn new_dynamic_library_completion_handler(
&self,
library: &ProtocolObject<dyn MTLLibrary>,
completion_handler: MTLNewDynamicLibraryCompletionHandler,
) -> Retained<ProtocolObject<dyn MTL4CompilerTask>>
where
Self: Sized,
{
unsafe {
msg_send![
self,
newDynamicLibrary: library,
completionHandler: &*completion_handler.0
]
}
}
fn new_dynamic_library_with_path_completion_handler(
&self,
path: &Path,
completion_handler: MTLNewDynamicLibraryCompletionHandler,
) -> Result<Retained<ProtocolObject<dyn MTL4CompilerTask>>, MetalError>
where
Self: Sized,
{
let url = crate::util::file_url(path, "newDynamicLibraryWithURL:completionHandler:")?;
Ok(unsafe {
msg_send![
self,
newDynamicLibraryWithURL: &*url,
completionHandler: &*completion_handler.0
]
})
}
fn new_compute_pipeline_state_with_descriptor_compiler_task_options_completion_handler(
&self,
descriptor: &MTL4ComputePipelineDescriptor,
compiler_task_options: Option<&MTL4CompilerTaskOptions>,
completion_handler: MTLNewComputePipelineStateCompletionHandler,
) -> Retained<ProtocolObject<dyn MTL4CompilerTask>>
where
Self: Sized,
{
unsafe {
msg_send![
self,
newComputePipelineStateWithDescriptor: descriptor,
compilerTaskOptions: compiler_task_options,
completionHandler: &*completion_handler.0
]
}
}
fn new_compute_pipeline_state_with_descriptor_dynamic_linking_descriptor_compiler_task_options_completion_handler(
&self,
descriptor: &MTL4ComputePipelineDescriptor,
dynamic_linking_descriptor: Option<&MTL4PipelineStageDynamicLinkingDescriptor>,
compiler_task_options: Option<&MTL4CompilerTaskOptions>,
completion_handler: MTLNewComputePipelineStateCompletionHandler,
) -> Retained<ProtocolObject<dyn MTL4CompilerTask>>
where
Self: Sized,
{
unsafe {
msg_send![
self,
newComputePipelineStateWithDescriptor: descriptor,
dynamicLinkingDescriptor: dynamic_linking_descriptor,
compilerTaskOptions: compiler_task_options,
completionHandler: &*completion_handler.0
]
}
}
fn new_render_pipeline_state_with_descriptor_compiler_task_options_completion_handler(
&self,
descriptor: &MTL4PipelineDescriptor,
compiler_task_options: Option<&MTL4CompilerTaskOptions>,
completion_handler: MTLNewRenderPipelineStateCompletionHandler,
) -> Retained<ProtocolObject<dyn MTL4CompilerTask>>
where
Self: Sized,
{
unsafe {
msg_send![
self,
newRenderPipelineStateWithDescriptor: descriptor,
compilerTaskOptions: compiler_task_options,
completionHandler: completion_handler.as_block()
]
}
}
fn new_render_pipeline_state_with_descriptor_dynamic_linking_descriptor_compiler_task_options_completion_handler(
&self,
descriptor: &MTL4PipelineDescriptor,
dynamic_linking_descriptor: Option<&MTL4RenderPipelineDynamicLinkingDescriptor>,
compiler_task_options: Option<&MTL4CompilerTaskOptions>,
completion_handler: MTLNewRenderPipelineStateCompletionHandler,
) -> Retained<ProtocolObject<dyn MTL4CompilerTask>>
where
Self: Sized,
{
unsafe {
msg_send![
self,
newRenderPipelineStateWithDescriptor: descriptor,
dynamicLinkingDescriptor: dynamic_linking_descriptor,
compilerTaskOptions: compiler_task_options,
completionHandler: completion_handler.as_block()
]
}
}
fn new_render_pipeline_state_by_specialization_with_descriptor_pipeline_completion_handler(
&self,
descriptor: &MTL4PipelineDescriptor,
pipeline: &ProtocolObject<dyn MTLRenderPipelineState>,
completion_handler: MTLNewRenderPipelineStateCompletionHandler,
) -> Retained<ProtocolObject<dyn MTL4CompilerTask>>
where
Self: Sized,
{
unsafe {
msg_send![
self,
newRenderPipelineStateBySpecializationWithDescriptor: descriptor,
pipeline: pipeline,
completionHandler: completion_handler.as_block()
]
}
}
fn new_binary_function_with_descriptor_compiler_task_options_completion_handler(
&self,
descriptor: &MTL4BinaryFunctionDescriptor,
compiler_task_options: Option<&MTL4CompilerTaskOptions>,
completion_handler: MTL4NewBinaryFunctionCompletionHandler,
) -> Retained<ProtocolObject<dyn MTL4CompilerTask>>
where
Self: Sized,
{
unsafe {
msg_send![
self,
newBinaryFunctionWithDescriptor: descriptor,
compilerTaskOptions: compiler_task_options,
completionHandler: &*completion_handler.0
]
}
}
fn new_machine_learning_pipeline_state_with_descriptor_completion_handler(
&self,
descriptor: &MTL4MachineLearningPipelineDescriptor,
completion_handler: MTL4NewMachineLearningPipelineStateCompletionHandler,
) -> Retained<ProtocolObject<dyn MTL4CompilerTask>>
where
Self: Sized,
{
unsafe {
msg_send![
self,
newMachineLearningPipelineStateWithDescriptor: descriptor,
completionHandler: &*completion_handler.0
]
}
}
}
impl<T: MTL4Compiler + Message> MTL4CompilerExt for T {}
#[cfg(test)]
mod tests {
use std::{
path::Path,
sync::{Arc, Mutex},
};
use objc2::{rc::Retained, runtime::ProtocolObject};
use objc2_foundation::{NSCopying, NSError, NSObjectProtocol, NSString};
use super::{
MTL4Compiler, MTL4CompilerDescriptor, MTL4CompilerExt, MTL4CompilerTaskOptions,
MTL4NewBinaryFunctionCompletionHandler, MTL4NewMachineLearningPipelineStateCompletionHandler,
MTLNewComputePipelineStateCompletionHandler, MTLNewDynamicLibraryCompletionHandler,
MTLNewLibraryCompletionHandler,
};
use crate::{
MTL4Archive, MTL4BinaryFunction, MTL4ComputePipelineDescriptor, MTL4LibraryDescriptor, MTLComputePipelineState,
MTLDynamicLibrary, MTLLibrary, MetalError,
};
#[test]
fn compiler_types_match_header_conformances() {
fn assert_nscopying<T: NSCopying>() {}
fn assert_nsobject<T: NSObjectProtocol>() {}
fn assert_send_sync<T: Send + Sync>() {}
assert_nscopying::<MTL4CompilerDescriptor>();
assert_nscopying::<MTL4CompilerTaskOptions>();
assert_nsobject::<MTL4CompilerDescriptor>();
assert_nsobject::<MTL4CompilerTaskOptions>();
assert_send_sync::<ProtocolObject<dyn MTL4Compiler>>();
}
#[test]
fn collection_string_and_path_methods_have_rust_native_signatures() {
let _: fn(&MTL4CompilerTaskOptions) -> Option<Box<[Retained<ProtocolObject<dyn MTL4Archive>>]>> =
MTL4CompilerTaskOptions::lookup_archives;
let _: fn(&ProtocolObject<dyn MTL4Compiler>) -> Option<String> =
<ProtocolObject<dyn MTL4Compiler> as MTL4CompilerExt>::label;
let _: fn(
&ProtocolObject<dyn MTL4Compiler>,
&Path,
) -> Result<Retained<ProtocolObject<dyn MTLDynamicLibrary>>, MetalError> =
<ProtocolObject<dyn MTL4Compiler> as MTL4CompilerExt>::new_dynamic_library_with_path;
}
#[test]
fn synchronous_errors_have_rust_native_signatures() {
let _: fn(
&ProtocolObject<dyn MTL4Compiler>,
&MTL4LibraryDescriptor,
) -> Result<Retained<ProtocolObject<dyn MTLLibrary>>, MetalError> =
<ProtocolObject<dyn MTL4Compiler> as MTL4CompilerExt>::new_library_with_descriptor_error;
let _: fn(
&ProtocolObject<dyn MTL4Compiler>,
&MTL4ComputePipelineDescriptor,
Option<&MTL4CompilerTaskOptions>,
) -> Result<Retained<ProtocolObject<dyn MTLComputePipelineState>>, MetalError> =
<ProtocolObject<dyn MTL4Compiler> as MTL4CompilerExt>::new_compute_pipeline_state_with_descriptor_compiler_task_options_error;
}
#[test]
fn standard_completion_handlers_deliver_rust_owned_errors() {
let library_handler = MTLNewLibraryCompletionHandler::new(
|_library: Option<Retained<ProtocolObject<dyn MTLLibrary>>>, _error: Option<MetalError>| {},
);
let dynamic_library_handler = MTLNewDynamicLibraryCompletionHandler::new(
|_library: Option<Retained<ProtocolObject<dyn MTLDynamicLibrary>>>, _error: Option<MetalError>| {},
);
let compute_handler = MTLNewComputePipelineStateCompletionHandler::new(
|_pipeline: Option<Retained<ProtocolObject<dyn MTLComputePipelineState>>>, _error: Option<MetalError>| {},
);
library_handler.0.call((core::ptr::null_mut(), core::ptr::null_mut()));
dynamic_library_handler.0.call((core::ptr::null_mut(), core::ptr::null_mut()));
compute_handler.0.call((core::ptr::null_mut(), core::ptr::null_mut()));
}
#[test]
fn sendable_handlers_accept_thread_safe_captures() {
let calls = Arc::new(Mutex::new(0));
let binary_calls = Arc::clone(&calls);
let binary_handler = MTL4NewBinaryFunctionCompletionHandler::new(move |function, error| {
assert!(function.is_none());
assert!(error.is_none());
*binary_calls.lock().unwrap() += 1;
});
let machine_learning_calls = Arc::clone(&calls);
let machine_learning_handler =
MTL4NewMachineLearningPipelineStateCompletionHandler::new(move |pipeline, error| {
assert!(pipeline.is_none());
assert!(error.is_none());
*machine_learning_calls.lock().unwrap() += 1;
});
binary_handler.0.call((core::ptr::null_mut(), core::ptr::null_mut()));
machine_learning_handler.0.call((core::ptr::null_mut(), core::ptr::null_mut()));
assert_eq!(*calls.lock().unwrap(), 2);
}
#[test]
fn borrowed_callback_error_is_retained_for_the_safe_handler() {
let received = Arc::new(Mutex::new(None));
let received_by_handler = Arc::clone(&received);
let handler = MTL4NewBinaryFunctionCompletionHandler::new(move |function, error| {
assert!(function.is_none());
*received_by_handler.lock().unwrap() = error;
});
let domain = NSString::from_str("mtl-rs.MTL4CompilerTests");
let error = unsafe { NSError::errorWithDomain_code_userInfo(&domain, 27, None) };
handler.0.call((
core::ptr::null_mut::<ProtocolObject<dyn MTL4BinaryFunction>>(),
(&*error as *const NSError).cast_mut(),
));
let received = received.lock().unwrap().take().unwrap();
assert_eq!(received.code(), 27);
drop(received);
assert_eq!(error.code(), 27);
}
}