Auto merge of #125525 - joboet:tls_accessor, r=cuviper

Make TLS accessors closures that return pointers

The current TLS macros generate a function that returns an `Option<&'static T>`. This is both risky as we lie about lifetimes, and necessitates that those functions are `unsafe`. By returning a `*const T` instead, the accessor function do not have safety requirements any longer and can be made closures without hassle. This PR does exactly that!

For native TLS, the closure approach makes it trivial to select the right accessor function at compile-time, which could result in a slight speed-up (I have the hope that the accessors are now simple enough for the MIR-inliner to kick in).
This commit is contained in:
bors 2024-06-04 05:03:52 +00:00
commit 27529d5c25
8 changed files with 132 additions and 275 deletions

View File

@ -21,43 +21,35 @@ impl<T> Storage<T> {
Storage { state: Cell::new(State::Initial), val: UnsafeCell::new(val) }
}
/// Get a reference to the TLS value. If the TLS variable has been destroyed,
/// `None` is returned.
/// Get a pointer to the TLS value. If the TLS variable has been destroyed,
/// a null pointer is returned.
///
/// The resulting pointer may not be used after thread destruction has
/// occurred.
///
/// # Safety
/// * The `self` reference must remain valid until the TLS destructor has been
/// run.
/// * The returned reference may only be used until thread destruction occurs
/// and may not be used after reentrant initialization has occurred.
///
// FIXME(#110897): return NonNull instead of lying about the lifetime.
/// The `self` reference must remain valid until the TLS destructor is run.
#[inline]
pub unsafe fn get(&self) -> Option<&'static T> {
pub unsafe fn get(&self) -> *const T {
match self.state.get() {
// SAFETY: as the state is not `Destroyed`, the value cannot have
// been destroyed yet. The reference fulfills the terms outlined
// above.
State::Alive => unsafe { Some(&*self.val.get()) },
State::Destroyed => None,
State::Alive => self.val.get(),
State::Destroyed => ptr::null(),
State::Initial => unsafe { self.initialize() },
}
}
#[cold]
unsafe fn initialize(&self) -> Option<&'static T> {
unsafe fn initialize(&self) -> *const T {
// Register the destructor
// SAFETY:
// * the destructor will be called at thread destruction.
// * the caller guarantees that `self` will be valid until that time.
// The caller guarantees that `self` will be valid until thread destruction.
unsafe {
register_dtor(ptr::from_ref(self).cast_mut().cast(), destroy::<T>);
}
self.state.set(State::Alive);
// SAFETY: as the state is not `Destroyed`, the value cannot have
// been destroyed yet. The reference fulfills the terms outlined
// above.
unsafe { Some(&*self.val.get()) }
self.val.get()
}
}

View File

@ -39,49 +39,31 @@ where
Storage { state: UnsafeCell::new(State::Initial) }
}
/// Get a reference to the TLS value, potentially initializing it with the
/// provided parameters. If the TLS variable has been destroyed, `None` is
/// returned.
/// Get a pointer to the TLS value, potentially initializing it with the
/// provided parameters. If the TLS variable has been destroyed, a null
/// pointer is returned.
///
/// The resulting pointer may not be used after reentrant inialialization
/// or thread destruction has occurred.
///
/// # Safety
/// * The `self` reference must remain valid until the TLS destructor is run,
/// at which point the returned reference is invalidated.
/// * The returned reference may only be used until thread destruction occurs
/// and may not be used after reentrant initialization has occurred.
///
// FIXME(#110897): return NonNull instead of lying about the lifetime.
/// The `self` reference must remain valid until the TLS destructor is run.
#[inline]
pub unsafe fn get_or_init(
&self,
i: Option<&mut Option<T>>,
f: impl FnOnce() -> T,
) -> Option<&'static T> {
// SAFETY:
// No mutable reference to the inner value exists outside the calls to
// `replace`. The lifetime of the returned reference fulfills the terms
// outlined above.
pub unsafe fn get_or_init(&self, i: Option<&mut Option<T>>, f: impl FnOnce() -> T) -> *const T {
let state = unsafe { &*self.state.get() };
match state {
State::Alive(v) => Some(v),
State::Destroyed(_) => None,
State::Alive(v) => v,
State::Destroyed(_) => ptr::null(),
State::Initial => unsafe { self.initialize(i, f) },
}
}
#[cold]
unsafe fn initialize(
&self,
i: Option<&mut Option<T>>,
f: impl FnOnce() -> T,
) -> Option<&'static T> {
unsafe fn initialize(&self, i: Option<&mut Option<T>>, f: impl FnOnce() -> T) -> *const T {
// Perform initialization
let v = i.and_then(Option::take).unwrap_or_else(f);
// SAFETY:
// If references to the inner value exist, they were created in `f`
// and are invalidated here. The caller promises to never use them
// after this.
let old = unsafe { self.state.get().replace(State::Alive(v)) };
match old {
// If the variable is not being recursively initialized, register
@ -92,12 +74,10 @@ where
val => drop(val),
}
// SAFETY:
// Initialization was completed and the state was set to `Alive`, so the
// reference fulfills the terms outlined above.
// SAFETY: the state was just set to `Alive`
unsafe {
let State::Alive(v) = &*self.state.get() else { unreachable_unchecked() };
Some(v)
v
}
}
}

View File

@ -52,32 +52,26 @@ pub macro thread_local_inner {
(@key $t:ty, const $init:expr) => {{
const __INIT: $t = $init;
#[inline]
#[deny(unsafe_op_in_unsafe_fn)]
unsafe fn __getit(
_init: $crate::option::Option<&mut $crate::option::Option<$t>>,
) -> $crate::option::Option<&'static $t> {
use $crate::thread::local_impl::EagerStorage;
unsafe {
use $crate::mem::needs_drop;
use $crate::ptr::addr_of;
use $crate::thread::LocalKey;
use $crate::thread::local_impl::EagerStorage;
LocalKey::new(const {
if needs_drop::<$t>() {
|_| {
#[thread_local]
static VAL: EagerStorage<$t> = EagerStorage::new(__INIT);
unsafe {
VAL.get()
}
} else {
|_| {
#[thread_local]
static VAL: $t = __INIT;
unsafe {
$crate::option::Option::Some(&*addr_of!(VAL))
&VAL
}
}
}
unsafe {
$crate::thread::LocalKey::new(__getit)
})
}
}},
@ -88,31 +82,26 @@ pub macro thread_local_inner {
$init
}
#[inline]
#[deny(unsafe_op_in_unsafe_fn)]
unsafe fn __getit(
init: $crate::option::Option<&mut $crate::option::Option<$t>>,
) -> $crate::option::Option<&'static $t> {
use $crate::thread::local_impl::LazyStorage;
unsafe {
use $crate::mem::needs_drop;
use $crate::thread::LocalKey;
use $crate::thread::local_impl::LazyStorage;
LocalKey::new(const {
if needs_drop::<$t>() {
|init| {
#[thread_local]
static VAL: LazyStorage<$t, ()> = LazyStorage::new();
unsafe {
VAL.get_or_init(init, __init)
}
} else {
|init| {
#[thread_local]
static VAL: LazyStorage<$t, !> = LazyStorage::new();
unsafe {
VAL.get_or_init(init, __init)
}
}
}
unsafe {
$crate::thread::LocalKey::new(__getit)
})
}
}},
($(#[$attr:meta])* $vis:vis $name:ident, $t:ty, $($init:tt)*) => {

View File

@ -16,30 +16,22 @@ pub macro thread_local_inner {
},
// used to generate the `LocalKey` value for `thread_local!`
(@key $t:ty, $init:expr) => {
{
(@key $t:ty, $init:expr) => {{
#[inline]
fn __init() -> $t { $init }
// `#[inline] does not work on windows-gnu due to linking errors around dllimports.
// See https://github.com/rust-lang/rust/issues/109797.
#[cfg_attr(not(windows), inline)]
unsafe fn __getit(
init: $crate::option::Option<&mut $crate::option::Option<$t>>,
) -> $crate::option::Option<&'static $t> {
unsafe {
use $crate::thread::LocalKey;
use $crate::thread::local_impl::Key;
static __KEY: Key<$t> = Key::new();
unsafe {
__KEY.get(init, __init)
// Inlining does not work on windows-gnu due to linking errors around
// dllimports. See https://github.com/rust-lang/rust/issues/109797.
LocalKey::new(#[cfg_attr(windows, inline(never))] |init| {
static VAL: Key<$t> = Key::new();
VAL.get(init, __init)
})
}
}
unsafe {
$crate::thread::LocalKey::new(__getit)
}
}
},
}},
($(#[$attr:meta])* $vis:vis $name:ident, $t:ty, $($init:tt)*) => {
$(#[$attr])* $vis const $name: $crate::thread::LocalKey<$t> =
$crate::thread::local_impl::thread_local_inner!(@key $t, $($init)*);
@ -67,38 +59,33 @@ impl<T: 'static> Key<T> {
Key { os: OsKey::new(Some(destroy_value::<T>)), marker: PhantomData }
}
/// Get the value associated with this key, initializating it if necessary.
/// Get a pointer to the TLS value, potentially initializing it with the
/// provided parameters. If the TLS variable has been destroyed, a null
/// pointer is returned.
///
/// # Safety
/// * the returned reference must not be used after recursive initialization
/// or thread destruction occurs.
pub unsafe fn get(
&'static self,
i: Option<&mut Option<T>>,
f: impl FnOnce() -> T,
) -> Option<&'static T> {
/// The resulting pointer may not be used after reentrant inialialization
/// or thread destruction has occurred.
pub fn get(&'static self, i: Option<&mut Option<T>>, f: impl FnOnce() -> T) -> *const T {
// SAFETY: (FIXME: get should actually be safe)
let ptr = unsafe { self.os.get() as *mut Value<T> };
if ptr.addr() > 1 {
// SAFETY: the check ensured the pointer is safe (its destructor
// is not running) + it is coming from a trusted source (self).
unsafe { Some(&(*ptr).value) }
unsafe { &(*ptr).value }
} else {
// SAFETY: At this point we are sure we have no value and so
// initializing (or trying to) is safe.
unsafe { self.try_initialize(ptr, i, f) }
self.try_initialize(ptr, i, f)
}
}
unsafe fn try_initialize(
fn try_initialize(
&'static self,
ptr: *mut Value<T>,
i: Option<&mut Option<T>>,
f: impl FnOnce() -> T,
) -> Option<&'static T> {
) -> *const T {
if ptr.addr() == 1 {
// destructor is running
return None;
return ptr::null();
}
let value = i.and_then(Option::take).unwrap_or_else(f);
@ -119,7 +106,7 @@ impl<T: 'static> Key<T> {
}
// SAFETY: We just created this value above.
unsafe { Some(&(*ptr).value) }
unsafe { &(*ptr).value }
}
}

View File

@ -13,19 +13,14 @@ pub macro thread_local_inner {
(@key $t:ty, const $init:expr) => {{
const __INIT: $t = $init;
#[inline]
#[deny(unsafe_op_in_unsafe_fn)]
unsafe fn __getit(
_init: $crate::option::Option<&mut $crate::option::Option<$t>>,
) -> $crate::option::Option<&'static $t> {
unsafe {
use $crate::thread::LocalKey;
use $crate::thread::local_impl::EagerStorage;
LocalKey::new(|_| {
static VAL: EagerStorage<$t> = EagerStorage { value: __INIT };
$crate::option::Option::Some(&VAL.value)
}
unsafe {
$crate::thread::LocalKey::new(__getit)
&VAL.value
})
}
}},
@ -34,19 +29,14 @@ pub macro thread_local_inner {
#[inline]
fn __init() -> $t { $init }
#[inline]
#[deny(unsafe_op_in_unsafe_fn)]
unsafe fn __getit(
init: $crate::option::Option<&mut $crate::option::Option<$t>>,
) -> $crate::option::Option<&'static $t> {
unsafe {
use $crate::thread::LocalKey;
use $crate::thread::local_impl::LazyStorage;
LocalKey::new(|init| {
static VAL: LazyStorage<$t> = LazyStorage::new();
unsafe { $crate::option::Option::Some(VAL.get(init, __init)) }
}
unsafe {
$crate::thread::LocalKey::new(__getit)
VAL.get(init, __init)
})
}
}},
($(#[$attr:meta])* $vis:vis $name:ident, $t:ty, $($init:tt)*) => {
@ -73,16 +63,13 @@ impl<T> LazyStorage<T> {
LazyStorage { value: UnsafeCell::new(None) }
}
/// Gets a reference to the contained value, initializing it if necessary.
/// Get a pointer to the TLS value, potentially initializing it with the
/// provided parameters.
///
/// # Safety
/// The returned reference may not be used after reentrant initialization has occurred.
/// The resulting pointer may not be used after reentrant inialialization
/// has occurred.
#[inline]
pub unsafe fn get(
&'static self,
i: Option<&mut Option<T>>,
f: impl FnOnce() -> T,
) -> &'static T {
pub fn get(&'static self, i: Option<&mut Option<T>>, f: impl FnOnce() -> T) -> *const T {
let value = unsafe { &*self.value.get() };
match value {
Some(v) => v,
@ -91,11 +78,7 @@ impl<T> LazyStorage<T> {
}
#[cold]
unsafe fn initialize(
&'static self,
i: Option<&mut Option<T>>,
f: impl FnOnce() -> T,
) -> &'static T {
fn initialize(&'static self, i: Option<&mut Option<T>>, f: impl FnOnce() -> T) -> *const T {
let value = i.and_then(Option::take).unwrap_or_else(f);
// Destroy the old value, after updating the TLS variable as the
// destructor might reference it.

View File

@ -123,7 +123,7 @@ pub struct LocalKey<T: 'static> {
// trivially devirtualizable by LLVM because the value of `inner` never
// changes and the constant should be readonly within a crate. This mainly
// only runs into problems when TLS statics are exported across crates.
inner: unsafe fn(Option<&mut Option<T>>) -> Option<&'static T>,
inner: fn(Option<&mut Option<T>>) -> *const T,
}
#[stable(feature = "std_debug", since = "1.16.0")]
@ -238,9 +238,7 @@ impl<T: 'static> LocalKey<T> {
issue = "none"
)]
#[rustc_const_unstable(feature = "thread_local_internals", issue = "none")]
pub const unsafe fn new(
inner: unsafe fn(Option<&mut Option<T>>) -> Option<&'static T>,
) -> LocalKey<T> {
pub const unsafe fn new(inner: fn(Option<&mut Option<T>>) -> *const T) -> LocalKey<T> {
LocalKey { inner }
}
@ -281,8 +279,7 @@ impl<T: 'static> LocalKey<T> {
where
F: FnOnce(&T) -> R,
{
// SAFETY: `inner` is safe to call within the lifetime of the thread
let thread_local = unsafe { (self.inner)(None).ok_or(AccessError)? };
let thread_local = unsafe { (self.inner)(None).as_ref().ok_or(AccessError)? };
Ok(f(thread_local))
}
@ -304,9 +301,8 @@ impl<T: 'static> LocalKey<T> {
{
let mut init = Some(init);
// SAFETY: `inner` is safe to call within the lifetime of the thread
let reference = unsafe {
(self.inner)(Some(&mut init)).expect(
(self.inner)(Some(&mut init)).as_ref().expect(
"cannot access a Thread Local Storage value \
during or after destruction",
)

View File

@ -1,6 +1,11 @@
// different number of duplicated diagnostics on different targets
//@ only-x86_64
//@ only-linux
// The specific errors produced depend the thread-local implementation.
// Run only on platforms with "fast" TLS.
//@ ignore-windows FIXME(#84933)
//@ ignore-wasm globals are used instead of thread locals
//@ ignore-emscripten globals are used instead of thread locals
//@ ignore-android does not use #[thread_local]
//@ ignore-nto does not use #[thread_local]
// Different number of duplicated diagnostics on different targets
//@ compile-flags: -Zdeduplicate-diagnostics=yes
#![allow(bare_trait_objects)]
@ -22,22 +27,18 @@ trait Tar<'t, 'k, I> {}
thread_local! {
static a: RefCell<HashMap<i32, Vec<Vec<Foo>>>> = RefCell::new(HashMap::new());
//~^ ERROR missing lifetime specifiers
//~| ERROR missing lifetime specifiers
}
thread_local! {
static b: RefCell<HashMap<i32, Vec<Vec<&Bar>>>> = RefCell::new(HashMap::new());
//~^ ERROR missing lifetime specifiers
//~| ERROR missing lifetime specifiers
}
thread_local! {
static c: RefCell<HashMap<i32, Vec<Vec<Qux<i32>>>>> = RefCell::new(HashMap::new());
//~^ ERROR missing lifetime specifiers
//~| ERROR missing lifetime specifiers
}
thread_local! {
static d: RefCell<HashMap<i32, Vec<Vec<&Tar<i32>>>>> = RefCell::new(HashMap::new());
//~^ ERROR missing lifetime specifiers
//~| ERROR missing lifetime specifiers
}
thread_local! {
@ -47,8 +48,7 @@ thread_local! {
thread_local! {
static f: RefCell<HashMap<i32, Vec<Vec<&Tar<'static, i32>>>>> = RefCell::new(HashMap::new());
//~^ ERROR trait takes 2 lifetime arguments but 1 lifetime argument was supplied
//~| ERROR missing lifetime
//~| ERROR missing lifetime
//~| ERROR missing lifetime specifier
}
fn main() {}

View File

@ -1,5 +1,5 @@
error[E0106]: missing lifetime specifiers
--> $DIR/missing-lifetime-specifier.rs:23:44
--> $DIR/missing-lifetime-specifier.rs:28:44
|
LL | static a: RefCell<HashMap<i32, Vec<Vec<Foo>>>> = RefCell::new(HashMap::new());
| ^^^ expected 2 lifetime parameters
@ -11,20 +11,7 @@ LL | static a: RefCell<HashMap<i32, Vec<Vec<Foo<'static, 'static>>>>> = RefC
| ++++++++++++++++++
error[E0106]: missing lifetime specifiers
--> $DIR/missing-lifetime-specifier.rs:23:44
|
LL | / thread_local! {
LL | | static a: RefCell<HashMap<i32, Vec<Vec<Foo>>>> = RefCell::new(HashMap::new());
| | ^^^ expected 2 lifetime parameters
LL | |
LL | |
LL | | }
| |_-
|
= help: this function's return type contains a borrowed value, but the signature does not say which one of `init`'s 3 lifetimes it is borrowed from
error[E0106]: missing lifetime specifiers
--> $DIR/missing-lifetime-specifier.rs:28:44
--> $DIR/missing-lifetime-specifier.rs:32:44
|
LL | static b: RefCell<HashMap<i32, Vec<Vec<&Bar>>>> = RefCell::new(HashMap::new());
| ^^^^ expected 2 lifetime parameters
@ -38,22 +25,7 @@ LL | static b: RefCell<HashMap<i32, Vec<Vec<&'static Bar<'static, 'static>>>
| +++++++ ++++++++++++++++++
error[E0106]: missing lifetime specifiers
--> $DIR/missing-lifetime-specifier.rs:28:44
|
LL | / thread_local! {
LL | | static b: RefCell<HashMap<i32, Vec<Vec<&Bar>>>> = RefCell::new(HashMap::new());
| | ^^^^ expected 2 lifetime parameters
| | |
| | expected named lifetime parameter
LL | |
LL | |
LL | | }
| |_-
|
= help: this function's return type contains a borrowed value, but the signature does not say which one of `init`'s 4 lifetimes it is borrowed from
error[E0106]: missing lifetime specifiers
--> $DIR/missing-lifetime-specifier.rs:33:47
--> $DIR/missing-lifetime-specifier.rs:36:47
|
LL | static c: RefCell<HashMap<i32, Vec<Vec<Qux<i32>>>>> = RefCell::new(HashMap::new());
| ^ expected 2 lifetime parameters
@ -65,20 +37,7 @@ LL | static c: RefCell<HashMap<i32, Vec<Vec<Qux<'static, 'static, i32>>>>> =
| +++++++++++++++++
error[E0106]: missing lifetime specifiers
--> $DIR/missing-lifetime-specifier.rs:33:47
|
LL | / thread_local! {
LL | | static c: RefCell<HashMap<i32, Vec<Vec<Qux<i32>>>>> = RefCell::new(HashMap::new());
| | ^ expected 2 lifetime parameters
LL | |
LL | |
LL | | }
| |_-
|
= help: this function's return type contains a borrowed value, but the signature does not say which one of `init`'s 3 lifetimes it is borrowed from
error[E0106]: missing lifetime specifiers
--> $DIR/missing-lifetime-specifier.rs:38:44
--> $DIR/missing-lifetime-specifier.rs:40:44
|
LL | static d: RefCell<HashMap<i32, Vec<Vec<&Tar<i32>>>>> = RefCell::new(HashMap::new());
| ^ ^ expected 2 lifetime parameters
@ -91,23 +50,8 @@ help: consider using the `'static` lifetime, but this is uncommon unless you're
LL | static d: RefCell<HashMap<i32, Vec<Vec<&'static Tar<'static, 'static, i32>>>>> = RefCell::new(HashMap::new());
| +++++++ +++++++++++++++++
error[E0106]: missing lifetime specifiers
--> $DIR/missing-lifetime-specifier.rs:38:44
|
LL | / thread_local! {
LL | | static d: RefCell<HashMap<i32, Vec<Vec<&Tar<i32>>>>> = RefCell::new(HashMap::new());
| | ^ ^ expected 2 lifetime parameters
| | |
| | expected named lifetime parameter
LL | |
LL | |
LL | | }
| |_-
|
= help: this function's return type contains a borrowed value, but the signature does not say which one of `init`'s 4 lifetimes it is borrowed from
error[E0106]: missing lifetime specifier
--> $DIR/missing-lifetime-specifier.rs:48:44
--> $DIR/missing-lifetime-specifier.rs:49:44
|
LL | static f: RefCell<HashMap<i32, Vec<Vec<&Tar<'static, i32>>>>> = RefCell::new(HashMap::new());
| ^ expected named lifetime parameter
@ -118,22 +62,8 @@ help: consider using the `'static` lifetime, but this is uncommon unless you're
LL | static f: RefCell<HashMap<i32, Vec<Vec<&'static Tar<'static, i32>>>>> = RefCell::new(HashMap::new());
| +++++++
error[E0106]: missing lifetime specifier
--> $DIR/missing-lifetime-specifier.rs:48:44
|
LL | / thread_local! {
LL | | static f: RefCell<HashMap<i32, Vec<Vec<&Tar<'static, i32>>>>> = RefCell::new(HashMap::new());
| | ^ expected named lifetime parameter
LL | |
LL | |
LL | |
LL | | }
| |_-
|
= help: this function's return type contains a borrowed value, but the signature does not say which one of `init`'s 3 lifetimes it is borrowed from
error[E0107]: union takes 2 lifetime arguments but 1 lifetime argument was supplied
--> $DIR/missing-lifetime-specifier.rs:44:44
--> $DIR/missing-lifetime-specifier.rs:45:44
|
LL | static e: RefCell<HashMap<i32, Vec<Vec<Qux<'static, i32>>>>> = RefCell::new(HashMap::new());
| ^^^ ------- supplied 1 lifetime argument
@ -141,7 +71,7 @@ LL | static e: RefCell<HashMap<i32, Vec<Vec<Qux<'static, i32>>>>> = RefCell:
| expected 2 lifetime arguments
|
note: union defined here, with 2 lifetime parameters: `'t`, `'k`
--> $DIR/missing-lifetime-specifier.rs:16:11
--> $DIR/missing-lifetime-specifier.rs:21:11
|
LL | pub union Qux<'t, 'k, I> {
| ^^^ -- --
@ -151,7 +81,7 @@ LL | static e: RefCell<HashMap<i32, Vec<Vec<Qux<'static, 'static, i32>>>>> =
| +++++++++
error[E0107]: trait takes 2 lifetime arguments but 1 lifetime argument was supplied
--> $DIR/missing-lifetime-specifier.rs:48:45
--> $DIR/missing-lifetime-specifier.rs:49:45
|
LL | static f: RefCell<HashMap<i32, Vec<Vec<&Tar<'static, i32>>>>> = RefCell::new(HashMap::new());
| ^^^ ------- supplied 1 lifetime argument
@ -159,7 +89,7 @@ LL | static f: RefCell<HashMap<i32, Vec<Vec<&Tar<'static, i32>>>>> = RefCell
| expected 2 lifetime arguments
|
note: trait defined here, with 2 lifetime parameters: `'t`, `'k`
--> $DIR/missing-lifetime-specifier.rs:20:7
--> $DIR/missing-lifetime-specifier.rs:25:7
|
LL | trait Tar<'t, 'k, I> {}
| ^^^ -- --
@ -168,7 +98,7 @@ help: add missing lifetime argument
LL | static f: RefCell<HashMap<i32, Vec<Vec<&Tar<'static, 'static, i32>>>>> = RefCell::new(HashMap::new());
| +++++++++
error: aborting due to 12 previous errors
error: aborting due to 7 previous errors
Some errors have detailed explanations: E0106, E0107.
For more information about an error, try `rustc --explain E0106`.