about summary refs log tree commit diff
path: root/src/libstd
diff options
context:
space:
mode:
authorbors <bors@rust-lang.org>2014-11-24 09:56:34 +0000
committerbors <bors@rust-lang.org>2014-11-24 09:56:34 +0000
commitbad1062caaaefe0963d7b8513786c8283e74f1e7 (patch)
treeb5bfc64c2cfa18969ec671e85b9c6c0c7053226b /src/libstd
parentf5b92b4b7a08290833e698ca374f3154e16e9714 (diff)
parenta9c1152c4bf72132806cb76045b3464d59db07da (diff)
downloadrust-bad1062caaaefe0963d7b8513786c8283e74f1e7.tar.gz
rust-bad1062caaaefe0963d7b8513786c8283e74f1e7.zip
auto merge of #19094 : alexcrichton/rust/rm-std-local-data, r=aturon
This commit removes the `std::local_data` module in favor of a new `std::thread_local`
module providing thread local storage. The module provides two variants of TLS:
one which owns its contents and one which is based on scoped references. Each
implementation has pros and cons listed in the documentation.

Both flavors have accessors through a function called `with` which yield a
reference to a closure provided. Both flavors also panic if a reference cannot
be yielded and provide a function to test whether an access would panic or not.
This is an implementation of [RFC 461][rfc] and full details can be found in
that RFC.

This is a breaking change due to the removal of the `std::local_data` module.
All users can migrate to the new tls system like so:

    thread_local!(static FOO: Rc<RefCell<Option<T>>> = Rc::new(RefCell::new(None)))

The old `local_data` module inherently contained the `Rc<RefCell<Option<T>>>` as
an implementation detail which must now be explicitly stated by users.

[rfc]: https://github.com/rust-lang/rfcs/pull/461
[breaking-change]
Diffstat (limited to 'src/libstd')
-rw-r--r--src/libstd/collections/hash/map.rs133
-rw-r--r--src/libstd/failure.rs15
-rw-r--r--src/libstd/io/stdio.rs31
-rw-r--r--src/libstd/lib.rs31
-rw-r--r--src/libstd/macros.rs22
-rw-r--r--src/libstd/rand/mod.rs31
-rw-r--r--src/libstd/sync/future.rs24
-rw-r--r--src/libstd/sys/common/mod.rs1
-rw-r--r--src/libstd/sys/common/thread_local.rs306
-rw-r--r--src/libstd/sys/unix/mod.rs7
-rw-r--r--src/libstd/sys/unix/thread_local.rs52
-rw-r--r--src/libstd/sys/windows/mod.rs7
-rw-r--r--src/libstd/sys/windows/thread_local.rs238
-rw-r--r--src/libstd/thread_local/mod.rs634
-rw-r--r--src/libstd/thread_local/scoped.rs261
15 files changed, 1634 insertions, 159 deletions
diff --git a/src/libstd/collections/hash/map.rs b/src/libstd/collections/hash/map.rs
index 69375e8d4f8..662ae913764 100644
--- a/src/libstd/collections/hash/map.rs
+++ b/src/libstd/collections/hash/map.rs
@@ -1471,7 +1471,7 @@ mod test_map {
         assert_eq!(*m.get(&2).unwrap(), 4);
     }
 
-    local_data_key!(drop_vector: RefCell<Vec<int>>)
+    thread_local!(static DROP_VECTOR: RefCell<Vec<int>> = RefCell::new(Vec::new()))
 
     #[deriving(Hash, PartialEq, Eq)]
     struct Dropable {
@@ -1480,8 +1480,9 @@ mod test_map {
 
     impl Dropable {
         fn new(k: uint) -> Dropable {
-            let v = drop_vector.get().unwrap();
-            v.borrow_mut().as_mut_slice()[k] += 1;
+            DROP_VECTOR.with(|slot| {
+                slot.borrow_mut()[k] += 1;
+            });
 
             Dropable { k: k }
         }
@@ -1489,8 +1490,9 @@ mod test_map {
 
     impl Drop for Dropable {
         fn drop(&mut self) {
-            let v = drop_vector.get().unwrap();
-            v.borrow_mut().as_mut_slice()[self.k] -= 1;
+            DROP_VECTOR.with(|slot| {
+                slot.borrow_mut()[self.k] -= 1;
+            });
         }
     }
 
@@ -1502,16 +1504,18 @@ mod test_map {
 
     #[test]
     fn test_drops() {
-        drop_vector.replace(Some(RefCell::new(Vec::from_elem(200, 0i))));
+        DROP_VECTOR.with(|slot| {
+            *slot.borrow_mut() = Vec::from_elem(200, 0i);
+        });
 
         {
             let mut m = HashMap::new();
 
-            let v = drop_vector.get().unwrap();
-            for i in range(0u, 200) {
-                assert_eq!(v.borrow().as_slice()[i], 0);
-            }
-            drop(v);
+            DROP_VECTOR.with(|v| {
+                for i in range(0u, 200) {
+                    assert_eq!(v.borrow().as_slice()[i], 0);
+                }
+            });
 
             for i in range(0u, 100) {
                 let d1 = Dropable::new(i);
@@ -1519,11 +1523,11 @@ mod test_map {
                 m.insert(d1, d2);
             }
 
-            let v = drop_vector.get().unwrap();
-            for i in range(0u, 200) {
-                assert_eq!(v.borrow().as_slice()[i], 1);
-            }
-            drop(v);
+            DROP_VECTOR.with(|v| {
+                for i in range(0u, 200) {
+                    assert_eq!(v.borrow().as_slice()[i], 1);
+                }
+            });
 
             for i in range(0u, 50) {
                 let k = Dropable::new(i);
@@ -1531,41 +1535,46 @@ mod test_map {
 
                 assert!(v.is_some());
 
-                let v = drop_vector.get().unwrap();
-                assert_eq!(v.borrow().as_slice()[i], 1);
-                assert_eq!(v.borrow().as_slice()[i+100], 1);
+                DROP_VECTOR.with(|v| {
+                    assert_eq!(v.borrow().as_slice()[i], 1);
+                    assert_eq!(v.borrow().as_slice()[i+100], 1);
+                });
             }
 
-            let v = drop_vector.get().unwrap();
-            for i in range(0u, 50) {
-                assert_eq!(v.borrow().as_slice()[i], 0);
-                assert_eq!(v.borrow().as_slice()[i+100], 0);
-            }
+            DROP_VECTOR.with(|v| {
+                for i in range(0u, 50) {
+                    assert_eq!(v.borrow().as_slice()[i], 0);
+                    assert_eq!(v.borrow().as_slice()[i+100], 0);
+                }
 
-            for i in range(50u, 100) {
-                assert_eq!(v.borrow().as_slice()[i], 1);
-                assert_eq!(v.borrow().as_slice()[i+100], 1);
-            }
+                for i in range(50u, 100) {
+                    assert_eq!(v.borrow().as_slice()[i], 1);
+                    assert_eq!(v.borrow().as_slice()[i+100], 1);
+                }
+            });
         }
 
-        let v = drop_vector.get().unwrap();
-        for i in range(0u, 200) {
-            assert_eq!(v.borrow().as_slice()[i], 0);
-        }
+        DROP_VECTOR.with(|v| {
+            for i in range(0u, 200) {
+                assert_eq!(v.borrow().as_slice()[i], 0);
+            }
+        });
     }
 
     #[test]
     fn test_move_iter_drops() {
-        drop_vector.replace(Some(RefCell::new(Vec::from_elem(200, 0i))));
+        DROP_VECTOR.with(|v| {
+            *v.borrow_mut() = Vec::from_elem(200, 0i);
+        });
 
         let hm = {
             let mut hm = HashMap::new();
 
-            let v = drop_vector.get().unwrap();
-            for i in range(0u, 200) {
-                assert_eq!(v.borrow().as_slice()[i], 0);
-            }
-            drop(v);
+            DROP_VECTOR.with(|v| {
+                for i in range(0u, 200) {
+                    assert_eq!(v.borrow().as_slice()[i], 0);
+                }
+            });
 
             for i in range(0u, 100) {
                 let d1 = Dropable::new(i);
@@ -1573,11 +1582,11 @@ mod test_map {
                 hm.insert(d1, d2);
             }
 
-            let v = drop_vector.get().unwrap();
-            for i in range(0u, 200) {
-                assert_eq!(v.borrow().as_slice()[i], 1);
-            }
-            drop(v);
+            DROP_VECTOR.with(|v| {
+                for i in range(0u, 200) {
+                    assert_eq!(v.borrow().as_slice()[i], 1);
+                }
+            });
 
             hm
         };
@@ -1588,31 +1597,33 @@ mod test_map {
         {
             let mut half = hm.into_iter().take(50);
 
-            let v = drop_vector.get().unwrap();
-            for i in range(0u, 200) {
-                assert_eq!(v.borrow().as_slice()[i], 1);
-            }
-            drop(v);
+            DROP_VECTOR.with(|v| {
+                for i in range(0u, 200) {
+                    assert_eq!(v.borrow().as_slice()[i], 1);
+                }
+            });
 
             for _ in half {}
 
-            let v = drop_vector.get().unwrap();
-            let nk = range(0u, 100).filter(|&i| {
-                v.borrow().as_slice()[i] == 1
-            }).count();
+            DROP_VECTOR.with(|v| {
+                let nk = range(0u, 100).filter(|&i| {
+                    v.borrow().as_slice()[i] == 1
+                }).count();
 
-            let nv = range(0u, 100).filter(|&i| {
-                v.borrow().as_slice()[i+100] == 1
-            }).count();
+                let nv = range(0u, 100).filter(|&i| {
+                    v.borrow().as_slice()[i+100] == 1
+                }).count();
 
-            assert_eq!(nk, 50);
-            assert_eq!(nv, 50);
+                assert_eq!(nk, 50);
+                assert_eq!(nv, 50);
+            });
         };
 
-        let v = drop_vector.get().unwrap();
-        for i in range(0u, 200) {
-            assert_eq!(v.borrow().as_slice()[i], 0);
-        }
+        DROP_VECTOR.with(|v| {
+            for i in range(0u, 200) {
+                assert_eq!(v.borrow().as_slice()[i], 0);
+            }
+        });
     }
 
     #[test]
diff --git a/src/libstd/failure.rs b/src/libstd/failure.rs
index c23e043c174..32a8be22902 100644
--- a/src/libstd/failure.rs
+++ b/src/libstd/failure.rs
@@ -12,10 +12,11 @@
 
 use alloc::boxed::Box;
 use any::{Any, AnyRefExt};
+use cell::RefCell;
 use fmt;
 use io::{Writer, IoResult};
 use kinds::Send;
-use option::{Some, None};
+use option::{Some, None, Option};
 use result::Ok;
 use rt::backtrace;
 use rustrt::{Stderr, Stdio};
@@ -25,7 +26,9 @@ use str::Str;
 use string::String;
 
 // Defined in this module instead of io::stdio so that the unwinding
-local_data_key!(pub local_stderr: Box<Writer + Send>)
+thread_local!(pub static LOCAL_STDERR: RefCell<Option<Box<Writer + Send>>> = {
+    RefCell::new(None)
+})
 
 impl Writer for Stdio {
     fn write(&mut self, bytes: &[u8]) -> IoResult<()> {
@@ -74,7 +77,8 @@ pub fn on_fail(obj: &Any + Send, file: &'static str, line: uint) {
     {
         let n = name.as_ref().map(|n| n.as_slice()).unwrap_or("<unnamed>");
 
-        match local_stderr.replace(None) {
+        let prev = LOCAL_STDERR.with(|s| s.borrow_mut().take());
+        match prev {
             Some(mut stderr) => {
                 // FIXME: what to do when the task printing panics?
                 let _ = writeln!(stderr,
@@ -83,7 +87,10 @@ pub fn on_fail(obj: &Any + Send, file: &'static str, line: uint) {
                 if backtrace::log_enabled() {
                     let _ = backtrace::write(&mut *stderr);
                 }
-                local_stderr.replace(Some(stderr));
+                let mut s = Some(stderr);
+                LOCAL_STDERR.with(|slot| {
+                    *slot.borrow_mut() = s.take();
+                });
             }
             None => {
                 let _ = writeln!(&mut err, "task '{}' panicked at '{}', {}:{}",
diff --git a/src/libstd/io/stdio.rs b/src/libstd/io/stdio.rs
index 7374668a69d..d450e9f1dce 100644
--- a/src/libstd/io/stdio.rs
+++ b/src/libstd/io/stdio.rs
@@ -29,22 +29,24 @@ out.write(b"Hello, world!");
 
 use self::StdSource::*;
 
-use failure::local_stderr;
+use boxed::Box;
+use cell::RefCell;
+use failure::LOCAL_STDERR;
 use fmt;
 use io::{Reader, Writer, IoResult, IoError, OtherIoError,
          standard_error, EndOfFile, LineBufferedWriter, BufferedReader};
 use iter::Iterator;
 use kinds::Send;
 use libc;
+use mem;
 use option::{Option, Some, None};
-use boxed::Box;
-use sys::{fs, tty};
 use result::{Ok, Err};
 use rustrt;
 use rustrt::local::Local;
 use rustrt::task::Task;
 use slice::SlicePrelude;
 use str::StrPrelude;
+use sys::{fs, tty};
 use uint;
 
 // And so begins the tale of acquiring a uv handle to a stdio stream on all
@@ -87,7 +89,9 @@ fn src<T>(fd: libc::c_int, _readable: bool, f: |StdSource| -> T) -> T {
     }
 }
 
-local_data_key!(local_stdout: Box<Writer + Send>)
+thread_local!(static LOCAL_STDOUT: RefCell<Option<Box<Writer + Send>>> = {
+    RefCell::new(None)
+})
 
 /// Creates a new non-blocking handle to the stdin of the current process.
 ///
@@ -167,7 +171,10 @@ pub fn stderr_raw() -> StdWriter {
 /// Note that this does not need to be called for all new tasks; the default
 /// output handle is to the process's stdout stream.
 pub fn set_stdout(stdout: Box<Writer + Send>) -> Option<Box<Writer + Send>> {
-    local_stdout.replace(Some(stdout)).and_then(|mut s| {
+    let mut new = Some(stdout);
+    LOCAL_STDOUT.with(|slot| {
+        mem::replace(&mut *slot.borrow_mut(), new.take())
+    }).and_then(|mut s| {
         let _ = s.flush();
         Some(s)
     })
@@ -182,7 +189,10 @@ pub fn set_stdout(stdout: Box<Writer + Send>) -> Option<Box<Writer + Send>> {
 /// Note that this does not need to be called for all new tasks; the default
 /// output handle is to the process's stderr stream.
 pub fn set_stderr(stderr: Box<Writer + Send>) -> Option<Box<Writer + Send>> {
-    local_stderr.replace(Some(stderr)).and_then(|mut s| {
+    let mut new = Some(stderr);
+    LOCAL_STDERR.with(|slot| {
+        mem::replace(&mut *slot.borrow_mut(), new.take())
+    }).and_then(|mut s| {
         let _ = s.flush();
         Some(s)
     })
@@ -200,11 +210,16 @@ pub fn set_stderr(stderr: Box<Writer + Send>) -> Option<Box<Writer + Send>> {
 //  })
 fn with_task_stdout(f: |&mut Writer| -> IoResult<()>) {
     let result = if Local::exists(None::<Task>) {
-        let mut my_stdout = local_stdout.replace(None).unwrap_or_else(|| {
+        let mut my_stdout = LOCAL_STDOUT.with(|slot| {
+            slot.borrow_mut().take()
+        }).unwrap_or_else(|| {
             box stdout() as Box<Writer + Send>
         });
         let result = f(&mut *my_stdout);
-        local_stdout.replace(Some(my_stdout));
+        let mut var = Some(my_stdout);
+        LOCAL_STDOUT.with(|slot| {
+            *slot.borrow_mut() = var.take();
+        });
         result
     } else {
         let mut io = rustrt::Stdout;
diff --git a/src/libstd/lib.rs b/src/libstd/lib.rs
index b35c49efdd8..77da727e94d 100644
--- a/src/libstd/lib.rs
+++ b/src/libstd/lib.rs
@@ -170,7 +170,6 @@ pub use core_collections::string;
 pub use core_collections::vec;
 
 pub use rustrt::c_str;
-pub use rustrt::local_data;
 
 pub use unicode::char;
 
@@ -209,17 +208,25 @@ pub mod prelude;
 #[path = "num/f32.rs"]   pub mod f32;
 #[path = "num/f64.rs"]   pub mod f64;
 
-pub mod rand;
-
 pub mod ascii;
 
-pub mod time;
-
 /* Common traits */
 
 pub mod error;
 pub mod num;
 
+/* Runtime and platform support */
+
+pub mod thread_local;
+pub mod c_vec;
+pub mod dynamic_lib;
+pub mod fmt;
+pub mod io;
+pub mod os;
+pub mod path;
+pub mod rand;
+pub mod time;
+
 /* Common data structures */
 
 pub mod collections;
@@ -230,15 +237,6 @@ pub mod hash;
 pub mod task;
 pub mod sync;
 
-/* Runtime and platform support */
-
-pub mod c_vec;
-pub mod dynamic_lib;
-pub mod os;
-pub mod io;
-pub mod path;
-pub mod fmt;
-
 #[cfg(unix)]
 #[path = "sys/unix/mod.rs"] mod sys;
 #[cfg(windows)]
@@ -263,10 +261,12 @@ mod std {
     pub use error; // used for try!()
     pub use fmt; // used for any formatting strings
     pub use io; // used for println!()
-    pub use local_data; // used for local_data_key!()
     pub use option; // used for bitflags!{}
     pub use rt; // used for panic!()
     pub use vec; // used for vec![]
+    pub use cell; // used for tls!
+    pub use thread_local; // used for thread_local!
+    pub use kinds; // used for tls!
 
     // The test runner calls ::std::os::args() but really wants realstd
     #[cfg(test)] pub use realstd::os as os;
@@ -276,4 +276,5 @@ mod std {
     pub use slice;
 
     pub use boxed; // used for vec![]
+
 }
diff --git a/src/libstd/macros.rs b/src/libstd/macros.rs
index 18c109d92a0..c3260225d0a 100644
--- a/src/libstd/macros.rs
+++ b/src/libstd/macros.rs
@@ -304,28 +304,6 @@ macro_rules! println(
     ($($arg:tt)*) => (format_args!(::std::io::stdio::println_args, $($arg)*))
 )
 
-/// Declare a task-local key with a specific type.
-///
-/// # Example
-///
-/// ```
-/// local_data_key!(my_integer: int)
-///
-/// my_integer.replace(Some(2));
-/// println!("{}", my_integer.get().map(|a| *a));
-/// ```
-#[macro_export]
-macro_rules! local_data_key(
-    ($name:ident: $ty:ty) => (
-        #[allow(non_upper_case_globals)]
-        static $name: ::std::local_data::Key<$ty> = &::std::local_data::KeyValueKey;
-    );
-    (pub $name:ident: $ty:ty) => (
-        #[allow(non_upper_case_globals)]
-        pub static $name: ::std::local_data::Key<$ty> = &::std::local_data::KeyValueKey;
-    );
-)
-
 /// Helper macro for unwrapping `Result` values while returning early with an
 /// error if the value of the expression is `Err`. For more information, see
 /// `std::io`.
diff --git a/src/libstd/rand/mod.rs b/src/libstd/rand/mod.rs
index 08eb7350bcf..f9f9147b107 100644
--- a/src/libstd/rand/mod.rs
+++ b/src/libstd/rand/mod.rs
@@ -226,7 +226,6 @@ use clone::Clone;
 use io::IoResult;
 use iter::Iterator;
 use mem;
-use option::{Some, None};
 use rc::Rc;
 use result::{Ok, Err};
 use vec::Vec;
@@ -337,24 +336,18 @@ pub struct TaskRng {
 /// explicitly select an RNG, e.g. `IsaacRng` or `Isaac64Rng`.
 pub fn task_rng() -> TaskRng {
     // used to make space in TLS for a random number generator
-    local_data_key!(TASK_RNG_KEY: Rc<RefCell<TaskRngInner>>)
-
-    match TASK_RNG_KEY.get() {
-        None => {
-            let r = match StdRng::new() {
-                Ok(r) => r,
-                Err(e) => panic!("could not initialize task_rng: {}", e)
-            };
-            let rng = reseeding::ReseedingRng::new(r,
-                                                   TASK_RNG_RESEED_THRESHOLD,
-                                                   TaskRngReseeder);
-            let rng = Rc::new(RefCell::new(rng));
-            TASK_RNG_KEY.replace(Some(rng.clone()));
-
-            TaskRng { rng: rng }
-        }
-        Some(rng) => TaskRng { rng: rng.clone() }
-    }
+    thread_local!(static TASK_RNG_KEY: Rc<RefCell<TaskRngInner>> = {
+        let r = match StdRng::new() {
+            Ok(r) => r,
+            Err(e) => panic!("could not initialize task_rng: {}", e)
+        };
+        let rng = reseeding::ReseedingRng::new(r,
+                                               TASK_RNG_RESEED_THRESHOLD,
+                                               TaskRngReseeder);
+        Rc::new(RefCell::new(rng))
+    })
+
+    TaskRng { rng: TASK_RNG_KEY.with(|t| t.clone()) }
 }
 
 impl Rng for TaskRng {
diff --git a/src/libstd/sync/future.rs b/src/libstd/sync/future.rs
index e37d1f83877..3e1ba8cebf8 100644
--- a/src/libstd/sync/future.rs
+++ b/src/libstd/sync/future.rs
@@ -210,28 +210,4 @@ mod test {
         });
         assert_eq!(rx.recv(), expected);
     }
-
-    #[test]
-    fn test_dropped_future_doesnt_panic() {
-        struct Bomb(Sender<bool>);
-
-        local_data_key!(LOCAL: Bomb)
-
-        impl Drop for Bomb {
-            fn drop(&mut self) {
-                let Bomb(ref tx) = *self;
-                tx.send(task::failing());
-            }
-        }
-
-        // Spawn a future, but drop it immediately. When we receive the result
-        // later on, we should never view the task as having panicked.
-        let (tx, rx) = channel();
-        drop(Future::spawn(proc() {
-            LOCAL.replace(Some(Bomb(tx)));
-        }));
-
-        // Make sure the future didn't panic the task.
-        assert!(!rx.recv());
-    }
 }
diff --git a/src/libstd/sys/common/mod.rs b/src/libstd/sys/common/mod.rs
index cacb128faa5..77edd7c5c4e 100644
--- a/src/libstd/sys/common/mod.rs
+++ b/src/libstd/sys/common/mod.rs
@@ -21,6 +21,7 @@ use collections;
 
 pub mod net;
 pub mod helper_thread;
+pub mod thread_local;
 
 // common error constructors
 
diff --git a/src/libstd/sys/common/thread_local.rs b/src/libstd/sys/common/thread_local.rs
new file mode 100644
index 00000000000..9ad38cbaf65
--- /dev/null
+++ b/src/libstd/sys/common/thread_local.rs
@@ -0,0 +1,306 @@
+// Copyright 2014 The Rust Project Developers. See the COPYRIGHT
+// file at the top-level directory of this distribution and at
+// http://rust-lang.org/COPYRIGHT.
+//
+// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
+// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
+// option. This file may not be copied, modified, or distributed
+// except according to those terms.
+
+//! OS-based thread local storage
+//!
+//! This module provides an implementation of OS-based thread local storage,
+//! using the native OS-provided facilities (think `TlsAlloc` or
+//! `pthread_setspecific`). The interface of this differs from the other types
+//! of thread-local-storage provided in this crate in that OS-based TLS can only
+//! get/set pointers,
+//!
+//! This module also provides two flavors of TLS. One is intended for static
+//! initialization, and does not contain a `Drop` implementation to deallocate
+//! the OS-TLS key. The other is a type which does implement `Drop` and hence
+//! has a safe interface.
+//!
+//! # Usage
+//!
+//! This module should likely not be used directly unless other primitives are
+//! being built on. types such as `thread_local::scoped::Key` are likely much
+//! more useful in practice than this OS-based version which likely requires
+//! unsafe code to interoperate with.
+//!
+//! # Example
+//!
+//! Using a dynamically allocated TLS key. Note that this key can be shared
+//! among many threads via an `Arc`.
+//!
+//! ```rust,ignore
+//! let key = Key::new(None);
+//! assert!(key.get().is_null());
+//! key.set(1 as *mut u8);
+//! assert!(!key.get().is_null());
+//!
+//! drop(key); // deallocate this TLS slot.
+//! ```
+//!
+//! Sometimes a statically allocated key is either required or easier to work
+//! with, however.
+//!
+//! ```rust,ignore
+//! static KEY: StaticKey = INIT;
+//!
+//! unsafe {
+//!     assert!(KEY.get().is_null());
+//!     KEY.set(1 as *mut u8);
+//! }
+//! ```
+
+#![allow(non_camel_case_types)]
+
+use prelude::*;
+
+use kinds::marker;
+use mem;
+use rustrt::exclusive::Exclusive;
+use rustrt;
+use sync::atomic::{mod, AtomicUint};
+use sync::{Once, ONCE_INIT};
+
+use sys::thread_local as imp;
+
+/// A type for TLS keys that are statically allocated.
+///
+/// This type is entirely `unsafe` to use as it does not protect against
+/// use-after-deallocation or use-during-deallocation.
+///
+/// The actual OS-TLS key is lazily allocated when this is used for the first
+/// time. The key is also deallocated when the Rust runtime exits or `destroy`
+/// is called, whichever comes first.
+///
+/// # Example
+///
+/// ```ignore
+/// use tls::os::{StaticKey, INIT};
+///
+/// static KEY: StaticKey = INIT;
+///
+/// unsafe {
+///     assert!(KEY.get().is_null());
+///     KEY.set(1 as *mut u8);
+/// }
+/// ```
+pub struct StaticKey {
+    /// Inner static TLS key (internals), created with by `INIT_INNER` in this
+    /// module.
+    pub inner: StaticKeyInner,
+    /// Destructor for the TLS value.
+    ///
+    /// See `Key::new` for information about when the destructor runs and how
+    /// it runs.
+    pub dtor: Option<unsafe extern fn(*mut u8)>,
+}
+
+/// Inner contents of `StaticKey`, created by the `INIT_INNER` constant.
+pub struct StaticKeyInner {
+    key: AtomicUint,
+    nc: marker::NoCopy,
+}
+
+/// A type for a safely managed OS-based TLS slot.
+///
+/// This type allocates an OS TLS key when it is initialized and will deallocate
+/// the key when it falls out of scope. When compared with `StaticKey`, this
+/// type is entirely safe to use.
+///
+/// Implementations will likely, however, contain unsafe code as this type only
+/// operates on `*mut u8`, an unsafe pointer.
+///
+/// # Example
+///
+/// ```rust,ignore
+/// use tls::os::Key;
+///
+/// let key = Key::new(None);
+/// assert!(key.get().is_null());
+/// key.set(1 as *mut u8);
+/// assert!(!key.get().is_null());
+///
+/// drop(key); // deallocate this TLS slot.
+/// ```
+pub struct Key {
+    key: imp::Key,
+}
+
+/// Constant initialization value for static TLS keys.
+///
+/// This value specifies no destructor by default.
+pub const INIT: StaticKey = StaticKey {
+    inner: INIT_INNER,
+    dtor: None,
+};
+
+/// Constant initialization value for the inner part of static TLS keys.
+///
+/// This value allows specific configuration of the destructor for a TLS key.
+pub const INIT_INNER: StaticKeyInner = StaticKeyInner {
+    key: atomic::INIT_ATOMIC_UINT,
+    nc: marker::NoCopy,
+};
+
+static INIT_KEYS: Once = ONCE_INIT;
+static mut KEYS: *mut Exclusive<Vec<imp::Key>> = 0 as *mut _;
+
+impl StaticKey {
+    /// Gets the value associated with this TLS key
+    ///
+    /// This will lazily allocate a TLS key from the OS if one has not already
+    /// been allocated.
+    #[inline]
+    pub unsafe fn get(&self) -> *mut u8 { imp::get(self.key()) }
+
+    /// Sets this TLS key to a new value.
+    ///
+    /// This will lazily allocate a TLS key from the OS if one has not already
+    /// been allocated.
+    #[inline]
+    pub unsafe fn set(&self, val: *mut u8) { imp::set(self.key(), val) }
+
+    /// Deallocates this OS TLS key.
+    ///
+    /// This function is unsafe as there is no guarantee that the key is not
+    /// currently in use by other threads or will not ever be used again.
+    ///
+    /// Note that this does *not* run the user-provided destructor if one was
+    /// specified at definition time. Doing so must be done manually.
+    pub unsafe fn destroy(&self) {
+        match self.inner.key.swap(0, atomic::SeqCst) {
+            0 => {}
+            n => { unregister_key(n as imp::Key); imp::destroy(n as imp::Key) }
+        }
+    }
+
+    #[inline]
+    unsafe fn key(&self) -> imp::Key {
+        match self.inner.key.load(atomic::Relaxed) {
+            0 => self.lazy_init() as imp::Key,
+            n => n as imp::Key
+        }
+    }
+
+    unsafe fn lazy_init(&self) -> uint {
+        let key = imp::create(self.dtor);
+        assert!(key != 0);
+        match self.inner.key.compare_and_swap(0, key as uint, atomic::SeqCst) {
+            // The CAS succeeded, so we've created the actual key
+            0 => {
+                register_key(key);
+                key as uint
+            }
+            // If someone beat us to the punch, use their key instead
+            n => { imp::destroy(key); n }
+        }
+    }
+}
+
+impl Key {
+    /// Create a new managed OS TLS key.
+    ///
+    /// This key will be deallocated when the key falls out of scope.
+    ///
+    /// The argument provided is an optionally-specified destructor for the
+    /// value of this TLS key. When a thread exits and the value for this key
+    /// is non-null the destructor will be invoked. The TLS value will be reset
+    /// to null before the destructor is invoked.
+    ///
+    /// Note that the destructor will not be run when the `Key` goes out of
+    /// scope.
+    #[inline]
+    pub fn new(dtor: Option<unsafe extern fn(*mut u8)>) -> Key {
+        Key { key: unsafe { imp::create(dtor) } }
+    }
+
+    /// See StaticKey::get
+    #[inline]
+    pub fn get(&self) -> *mut u8 {
+        unsafe { imp::get(self.key) }
+    }
+
+    /// See StaticKey::set
+    #[inline]
+    pub fn set(&self, val: *mut u8) {
+        unsafe { imp::set(self.key, val) }
+    }
+}
+
+impl Drop for Key {
+    fn drop(&mut self) {
+        unsafe { imp::destroy(self.key) }
+    }
+}
+
+fn init_keys() {
+    let keys = box Exclusive::new(Vec::<imp::Key>::new());
+    unsafe {
+        KEYS = mem::transmute(keys);
+    }
+
+    rustrt::at_exit(proc() unsafe {
+        let keys: Box<Exclusive<Vec<imp::Key>>> = mem::transmute(KEYS);
+        KEYS = 0 as *mut _;
+        let keys = keys.lock();
+        for key in keys.iter() {
+            imp::destroy(*key);
+        }
+    });
+}
+
+fn register_key(key: imp::Key) {
+    INIT_KEYS.doit(init_keys);
+    let mut keys = unsafe { (*KEYS).lock() };
+    keys.push(key);
+}
+
+fn unregister_key(key: imp::Key) {
+    INIT_KEYS.doit(init_keys);
+    let mut keys = unsafe { (*KEYS).lock() };
+    keys.retain(|k| *k != key);
+}
+
+#[cfg(test)]
+mod tests {
+    use prelude::*;
+    use super::{Key, StaticKey, INIT_INNER};
+
+    fn assert_sync<T: Sync>() {}
+    fn assert_send<T: Send>() {}
+
+    #[test]
+    fn smoke() {
+        assert_sync::<Key>();
+        assert_send::<Key>();
+
+        let k1 = Key::new(None);
+        let k2 = Key::new(None);
+        assert!(k1.get().is_null());
+        assert!(k2.get().is_null());
+        k1.set(1 as *mut _);
+        k2.set(2 as *mut _);
+        assert_eq!(k1.get() as uint, 1);
+        assert_eq!(k2.get() as uint, 2);
+    }
+
+    #[test]
+    fn statik() {
+        static K1: StaticKey = StaticKey { inner: INIT_INNER, dtor: None };
+        static K2: StaticKey = StaticKey { inner: INIT_INNER, dtor: None };
+
+        unsafe {
+            assert!(K1.get().is_null());
+            assert!(K2.get().is_null());
+            K1.set(1 as *mut _);
+            K2.set(2 as *mut _);
+            assert_eq!(K1.get() as uint, 1);
+            assert_eq!(K2.get() as uint, 2);
+        }
+    }
+}
+
diff --git a/src/libstd/sys/unix/mod.rs b/src/libstd/sys/unix/mod.rs
index 664a6a1e70c..7a5317b578d 100644
--- a/src/libstd/sys/unix/mod.rs
+++ b/src/libstd/sys/unix/mod.rs
@@ -34,14 +34,15 @@ macro_rules! helper_init( (static $name:ident: Helper<$m:ty>) => (
 
 pub mod c;
 pub mod fs;
+pub mod helper_signal;
 pub mod os;
-pub mod tcp;
-pub mod udp;
 pub mod pipe;
-pub mod helper_signal;
 pub mod process;
+pub mod tcp;
 pub mod timer;
+pub mod thread_local;
 pub mod tty;
+pub mod udp;
 
 pub mod addrinfo {
     pub use sys_common::net::get_host_addresses;
diff --git a/src/libstd/sys/unix/thread_local.rs b/src/libstd/sys/unix/thread_local.rs
new file mode 100644
index 00000000000..b300e93eeb6
--- /dev/null
+++ b/src/libstd/sys/unix/thread_local.rs
@@ -0,0 +1,52 @@
+// Copyright 2014 The Rust Project Developers. See the COPYRIGHT
+// file at the top-level directory of this distribution and at
+// http://rust-lang.org/COPYRIGHT.
+//
+// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
+// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
+// option. This file may not be copied, modified, or distributed
+// except according to those terms.
+
+use prelude::*;
+use libc::c_int;
+
+pub type Key = pthread_key_t;
+
+#[inline]
+pub unsafe fn create(dtor: Option<unsafe extern fn(*mut u8)>) -> Key {
+    let mut key = 0;
+    assert_eq!(pthread_key_create(&mut key, dtor), 0);
+    return key;
+}
+
+#[inline]
+pub unsafe fn set(key: Key, value: *mut u8) {
+    let r = pthread_setspecific(key, value);
+    debug_assert_eq!(r, 0);
+}
+
+#[inline]
+pub unsafe fn get(key: Key) -> *mut u8 {
+    pthread_getspecific(key)
+}
+
+#[inline]
+pub unsafe fn destroy(key: Key) {
+    let r = pthread_key_delete(key);
+    debug_assert_eq!(r, 0);
+}
+
+#[cfg(target_os = "macos")]
+type pthread_key_t = ::libc::c_ulong;
+
+#[cfg(not(target_os = "macos"))]
+type pthread_key_t = ::libc::c_uint;
+
+extern {
+    fn pthread_key_create(key: *mut pthread_key_t,
+                          dtor: Option<unsafe extern fn(*mut u8)>) -> c_int;
+    fn pthread_key_delete(key: pthread_key_t) -> c_int;
+    fn pthread_getspecific(key: pthread_key_t) -> *mut u8;
+    fn pthread_setspecific(key: pthread_key_t, value: *mut u8) -> c_int;
+}
diff --git a/src/libstd/sys/windows/mod.rs b/src/libstd/sys/windows/mod.rs
index 815ace21f87..a785ccfe804 100644
--- a/src/libstd/sys/windows/mod.rs
+++ b/src/libstd/sys/windows/mod.rs
@@ -35,14 +35,15 @@ macro_rules! helper_init( (static $name:ident: Helper<$m:ty>) => (
 
 pub mod c;
 pub mod fs;
+pub mod helper_signal;
 pub mod os;
-pub mod tcp;
-pub mod udp;
 pub mod pipe;
-pub mod helper_signal;
 pub mod process;
+pub mod tcp;
+pub mod thread_local;
 pub mod timer;
 pub mod tty;
+pub mod udp;
 
 pub mod addrinfo {
     pub use sys_common::net::get_host_addresses;
diff --git a/src/libstd/sys/windows/thread_local.rs b/src/libstd/sys/windows/thread_local.rs
new file mode 100644
index 00000000000..b841f6d3a2b
--- /dev/null
+++ b/src/libstd/sys/windows/thread_local.rs
@@ -0,0 +1,238 @@
+// Copyright 2014 The Rust Project Developers. See the COPYRIGHT
+// file at the top-level directory of this distribution and at
+// http://rust-lang.org/COPYRIGHT.
+//
+// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
+// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
+// option. This file may not be copied, modified, or distributed
+// except according to those terms.
+
+use prelude::*;
+
+use libc::types::os::arch::extra::{DWORD, LPVOID, BOOL};
+
+use mem;
+use rustrt;
+use rustrt::exclusive::Exclusive;
+use sync::{ONCE_INIT, Once};
+
+pub type Key = DWORD;
+pub type Dtor = unsafe extern fn(*mut u8);
+
+// Turns out, like pretty much everything, Windows is pretty close the
+// functionality that Unix provides, but slightly different! In the case of
+// TLS, Windows does not provide an API to provide a destructor for a TLS
+// variable. This ends up being pretty crucial to this implementation, so we
+// need a way around this.
+//
+// The solution here ended up being a little obscure, but fear not, the
+// internet has informed me [1][2] that this solution is not unique (no way
+// I could have thought of it as well!). The key idea is to insert some hook
+// somewhere to run arbitrary code on thread termination. With this in place
+// we'll be able to run anything we like, including all TLS destructors!
+//
+// To accomplish this feat, we perform a number of tasks, all contained
+// within this module:
+//
+// * All TLS destructors are tracked by *us*, not the windows runtime. This
+//   means that we have a global list of destructors for each TLS key that
+//   we know about.
+// * When a TLS key is destroyed, we're sure to remove it from the dtor list
+//   if it's in there.
+// * When a thread exits, we run over the entire list and run dtors for all
+//   non-null keys. This attempts to match Unix semantics in this regard.
+//
+// This ends up having the overhead of using a global list, having some
+// locks here and there, and in general just adding some more code bloat. We
+// attempt to optimize runtime by forgetting keys that don't have
+// destructors, but this only gets us so far.
+//
+// For more details and nitty-gritty, see the code sections below!
+//
+// [1]: http://www.codeproject.com/Articles/8113/Thread-Local-Storage-The-C-Way
+// [2]: https://github.com/ChromiumWebApps/chromium/blob/master/base
+//                        /threading/thread_local_storage_win.cc#L42
+
+static INIT_DTORS: Once = ONCE_INIT;
+static mut DTORS: *mut Exclusive<Vec<(Key, Dtor)>> = 0 as *mut _;
+
+// -------------------------------------------------------------------------
+// Native bindings
+//
+// This section is just raw bindings to the native functions that Windows
+// provides, There's a few extra calls to deal with destructors.
+
+#[inline]
+pub unsafe fn create(dtor: Option<Dtor>) -> Key {
+    const TLS_OUT_OF_INDEXES: DWORD = 0xFFFFFFFF;
+    let key = TlsAlloc();
+    assert!(key != TLS_OUT_OF_INDEXES);
+    match dtor {
+        Some(f) => register_dtor(key, f),
+        None => {}
+    }
+    return key;
+}
+
+#[inline]
+pub unsafe fn set(key: Key, value: *mut u8) {
+    let r = TlsSetValue(key, value as LPVOID);
+    debug_assert!(r != 0);
+}
+
+#[inline]
+pub unsafe fn get(key: Key) -> *mut u8 {
+    TlsGetValue(key) as *mut u8
+}
+
+#[inline]
+pub unsafe fn destroy(key: Key) {
+    if unregister_dtor(key) {
+        // FIXME: Currently if a key has a destructor associated with it we
+        // can't actually ever unregister it. If we were to
+        // unregister it, then any key destruction would have to be
+        // serialized with respect to actually running destructors.
+        //
+        // We want to avoid a race where right before run_dtors runs
+        // some destructors TlsFree is called. Allowing the call to
+        // TlsFree would imply that the caller understands that *all
+        // known threads* are not exiting, which is quite a difficult
+        // thing to know!
+        //
+        // For now we just leak all keys with dtors to "fix" this.
+        // Note that source [2] above shows precedent for this sort
+        // of strategy.
+    } else {
+        let r = TlsFree(key);
+        debug_assert!(r != 0);
+    }
+}
+
+extern "system" {
+    fn TlsAlloc() -> DWORD;
+    fn TlsFree(dwTlsIndex: DWORD) -> BOOL;
+    fn TlsGetValue(dwTlsIndex: DWORD) -> LPVOID;
+    fn TlsSetValue(dwTlsIndex: DWORD, lpTlsvalue: LPVOID) -> BOOL;
+}
+
+// -------------------------------------------------------------------------
+// Dtor registration
+//
+// These functions are associated with registering and unregistering
+// destructors. They're pretty simple, they just push onto a vector and scan
+// a vector currently.
+//
+// FIXME: This could probably be at least a little faster with a BTree.
+
+fn init_dtors() {
+    let dtors = box Exclusive::new(Vec::<(Key, Dtor)>::new());
+    unsafe {
+        DTORS = mem::transmute(dtors);
+    }
+
+    rustrt::at_exit(proc() unsafe {
+        mem::transmute::<_, Box<Exclusive<Vec<(Key, Dtor)>>>>(DTORS);
+        DTORS = 0 as *mut _;
+    });
+}
+
+unsafe fn register_dtor(key: Key, dtor: Dtor) {
+    INIT_DTORS.doit(init_dtors);
+    let mut dtors = (*DTORS).lock();
+    dtors.push((key, dtor));
+}
+
+unsafe fn unregister_dtor(key: Key) -> bool {
+    if DTORS.is_null() { return false }
+    let mut dtors = (*DTORS).lock();
+    let before = dtors.len();
+    dtors.retain(|&(k, _)| k != key);
+    dtors.len() != before
+}
+
+// -------------------------------------------------------------------------
+// Where the Magic (TM) Happens
+//
+// If you're looking at this code, and wondering "what is this doing?",
+// you're not alone! I'll try to break this down step by step:
+//
+// # What's up with CRT$XLB?
+//
+// For anything about TLS destructors to work on Windows, we have to be able
+// to run *something* when a thread exits. To do so, we place a very special
+// static in a very special location. If this is encoded in just the right
+// way, the kernel's loader is apparently nice enough to run some function
+// of ours whenever a thread exits! How nice of the kernel!
+//
+// Lots of detailed information can be found in source [1] above, but the
+// gist of it is that this is leveraging a feature of Microsoft's PE format
+// (executable format) which is not actually used by any compilers today.
+// This apparently translates to any callbacks in the ".CRT$XLB" section
+// being run on certain events.
+//
+// So after all that, we use the compiler's #[link_section] feature to place
+// a callback pointer into the magic section so it ends up being called.
+//
+// # What's up with this callback?
+//
+// The callback specified receives a number of parameters from... someone!
+// (the kernel? the runtime? I'm not qute sure!) There are a few events that
+// this gets invoked for, but we're currentl only interested on when a
+// thread or a process "detaches" (exits). The process part happens for the
+// last thread and the thread part happens for any normal thread.
+//
+// # Ok, what's up with running all these destructors?
+//
+// This will likely need to be improved over time, but this function
+// attempts a "poor man's" destructor callback system. To do this we clone a
+// local copy of the dtor list to start out with. This is our fudgy attempt
+// to not hold the lock while destructors run and not worry about the list
+// changing while we're looking at it.
+//
+// Once we've got a list of what to run, we iterate over all keys, check
+// their values, and then run destructors if the values turn out to be non
+// null (setting them to null just beforehand). We do this a few times in a
+// loop to basically match Unix semantics. If we don't reach a fixed point
+// after a short while then we just inevitably leak something most likely.
+//
+// # The article mentions crazy stuff about "/INCLUDE"?
+//
+// It sure does! This seems to work for now, so maybe we'll just run into
+// that if we start linking with msvc?
+
+#[link_section = ".CRT$XLB"]
+#[linkage = "external"]
+#[allow(warnings)]
+pub static p_thread_callback: unsafe extern "system" fn(LPVOID, DWORD,
+                                                        LPVOID) =
+        on_tls_callback;
+
+#[allow(warnings)]
+unsafe extern "system" fn on_tls_callback(h: LPVOID,
+                                          dwReason: DWORD,
+                                          pv: LPVOID) {
+    const DLL_THREAD_DETACH: DWORD = 3;
+    const DLL_PROCESS_DETACH: DWORD = 0;
+    if dwReason == DLL_THREAD_DETACH || dwReason == DLL_PROCESS_DETACH {
+        run_dtors();
+    }
+}
+
+unsafe fn run_dtors() {
+    if DTORS.is_null() { return }
+    let mut any_run = true;
+    for _ in range(0, 5i) {
+        if !any_run { break }
+        any_run = false;
+        let dtors = (*DTORS).lock().iter().map(|p| *p).collect::<Vec<_>>();
+        for &(key, dtor) in dtors.iter() {
+            let ptr = TlsGetValue(key);
+            if !ptr.is_null() {
+                TlsSetValue(key, 0 as *mut _);
+                dtor(ptr as *mut _);
+                any_run = true;
+            }
+        }
+    }
+}
diff --git a/src/libstd/thread_local/mod.rs b/src/libstd/thread_local/mod.rs
new file mode 100644
index 00000000000..e2e8461ea54
--- /dev/null
+++ b/src/libstd/thread_local/mod.rs
@@ -0,0 +1,634 @@
+// Copyright 2014 The Rust Project Developers. See the COPYRIGHT
+// file at the top-level directory of this distribution and at
+// http://rust-lang.org/COPYRIGHT.
+//
+// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
+// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
+// option. This file may not be copied, modified, or distributed
+// except according to those terms.
+
+//! Thread local storage
+//!
+//! This module provides an implementation of thread local storage for Rust
+//! programs. Thread local storage is a method of storing data into a global
+//! variable which each thread in the program will have its own copy of.
+//! Threads do not share this data, so accesses do not need to be synchronized.
+//!
+//! At a high level, this module provides two variants of storage:
+//!
+//! * Owning thread local storage. This is a type of thread local key which
+//!   owns the value that it contains, and will destroy the value when the
+//!   thread exits. This variant is created with the `thread_local!` macro and
+//!   can contain any value which is `'static` (no borrowed pointers.
+//!
+//! * Scoped thread local storage. This type of key is used to store a reference
+//!   to a value into local storage temporarily for the scope of a function
+//!   call. There are no restrictions on what types of values can be placed
+//!   into this key.
+//!
+//! Both forms of thread local storage provide an accessor function, `with`,
+//! which will yield a shared reference to the value to the specified
+//! closure. Thread local keys only allow shared access to values as there is no
+//! way to guarantee uniqueness if a mutable borrow was allowed. Most values
+//! will want to make use of some form of **interior mutability** through the
+//! `Cell` or `RefCell` types.
+
+#![macro_escape]
+#![experimental]
+
+use prelude::*;
+
+use cell::UnsafeCell;
+
+// Sure wish we had macro hygiene, no?
+#[doc(hidden)] pub use self::imp::Key as KeyInner;
+#[doc(hidden)] pub use self::imp::destroy_value;
+#[doc(hidden)] pub use sys_common::thread_local::INIT_INNER as OS_INIT_INNER;
+#[doc(hidden)] pub use sys_common::thread_local::StaticKey as OsStaticKey;
+
+pub mod scoped;
+
+/// A thread local storage key which owns its contents.
+///
+/// This key uses the fastest possible implementation available to it for the
+/// target platform. It is instantiated with the `thread_local!` macro and the
+/// primary method is the `with` method.
+///
+/// The `with` method yields a reference to the contained value which cannot be
+/// sent across tasks or escape the given closure.
+///
+/// # Initialization and Destruction
+///
+/// Initialization is dynamically performed on the first call to `with()`
+/// within a thread, and values support destructors which will be run when a
+/// thread exits.
+///
+/// # Example
+///
+/// ```
+/// use std::cell::RefCell;
+///
+/// thread_local!(static FOO: RefCell<uint> = RefCell::new(1));
+///
+/// FOO.with(|f| {
+///     assert_eq!(*f.borrow(), 1);
+///     *f.borrow_mut() = 2;
+/// });
+///
+/// // each thread starts out with the initial value of 1
+/// spawn(proc() {
+///     FOO.with(|f| {
+///         assert_eq!(*f.borrow(), 1);
+///         *f.borrow_mut() = 3;
+///     });
+/// });
+///
+/// // we retain our original value of 2 despite the child thread
+/// FOO.with(|f| {
+///     assert_eq!(*f.borrow(), 2);
+/// });
+/// ```
+pub struct Key<T> {
+    // The key itself may be tagged with #[thread_local], and this `Key` is
+    // stored as a `static`, and it's not valid for a static to reference the
+    // address of another thread_local static. For this reason we kinda wonkily
+    // work around this by generating a shim function which will give us the
+    // address of the inner TLS key at runtime.
+    //
+    // This is trivially devirtualizable by LLVM because we never store anything
+    // to this field and rustc can declare the `static` as constant as well.
+    #[doc(hidden)]
+    pub inner: fn() -> &'static KeyInner<UnsafeCell<Option<T>>>,
+
+    // initialization routine to invoke to create a value
+    #[doc(hidden)]
+    pub init: fn() -> T,
+}
+
+/// Declare a new thread local storage key of type `std::thread_local::Key`.
+#[macro_export]
+#[doc(hidden)]
+macro_rules! thread_local(
+    (static $name:ident: $t:ty = $init:expr) => (
+        static $name: ::std::thread_local::Key<$t> = {
+            use std::cell::UnsafeCell as __UnsafeCell;
+            use std::thread_local::KeyInner as __KeyInner;
+            use std::option::Option as __Option;
+            use std::option::None as __None;
+
+            __thread_local_inner!(static __KEY: __UnsafeCell<__Option<$t>> = {
+                __UnsafeCell { value: __None }
+            })
+            fn __init() -> $t { $init }
+            fn __getit() -> &'static __KeyInner<__UnsafeCell<__Option<$t>>> {
+                &__KEY
+            }
+            ::std::thread_local::Key { inner: __getit, init: __init }
+        };
+    );
+    (pub static $name:ident: $t:ty = $init:expr) => (
+        pub static $name: ::std::thread_local::Key<$t> = {
+            use std::cell::UnsafeCell as __UnsafeCell;
+            use std::thread_local::KeyInner as __KeyInner;
+            use std::option::Option as __Option;
+            use std::option::None as __None;
+
+            __thread_local_inner!(static __KEY: __UnsafeCell<__Option<$t>> = {
+                __UnsafeCell { value: __None }
+            })
+            fn __init() -> $t { $init }
+            fn __getit() -> &'static __KeyInner<__UnsafeCell<__Option<$t>>> {
+                &__KEY
+            }
+            ::std::thread_local::Key { inner: __getit, init: __init }
+        };
+    );
+)
+
+// Macro pain #4586:
+//
+// When cross compiling, rustc will load plugins and macros from the *host*
+// platform before search for macros from the target platform. This is primarily
+// done to detect, for example, plugins. Ideally the macro below would be
+// defined once per module below, but unfortunately this means we have the
+// following situation:
+//
+// 1. We compile libstd for x86_64-unknown-linux-gnu, this thread_local!() macro
+//    will inject #[thread_local] statics.
+// 2. We then try to compile a program for arm-linux-androideabi
+// 3. The compiler has a host of linux and a target of android, so it loads
+//    macros from the *linux* libstd.
+// 4. The macro generates a #[thread_local] field, but the android libstd does
+//    not use #[thread_local]
+// 5. Compile error about structs with wrong fields.
+//
+// To get around this, we're forced to inject the #[cfg] logic into the macro
+// itself. Woohoo.
+
+#[macro_export]
+macro_rules! __thread_local_inner(
+    (static $name:ident: $t:ty = $init:expr) => (
+        #[cfg_attr(any(target_os = "macos", target_os = "linux"), thread_local)]
+        static $name: ::std::thread_local::KeyInner<$t> =
+            __thread_local_inner!($init, $t);
+    );
+    (pub static $name:ident: $t:ty = $init:expr) => (
+        #[cfg_attr(any(target_os = "macos", target_os = "linux"), thread_local)]
+        pub static $name: ::std::thread_local::KeyInner<$t> =
+            __thread_local_inner!($init, $t);
+    );
+    ($init:expr, $t:ty) => ({
+        #[cfg(any(target_os = "macos", target_os = "linux"))]
+        const INIT: ::std::thread_local::KeyInner<$t> = {
+            ::std::thread_local::KeyInner {
+                inner: ::std::cell::UnsafeCell { value: $init },
+                dtor_registered: ::std::cell::UnsafeCell { value: false },
+                dtor_running: ::std::cell::UnsafeCell { value: false },
+                marker: ::std::kinds::marker::NoCopy,
+            }
+        };
+
+        #[cfg(not(any(target_os = "macos", target_os = "linux")))]
+        const INIT: ::std::thread_local::KeyInner<$t> = {
+            unsafe extern fn __destroy(ptr: *mut u8) {
+                ::std::thread_local::destroy_value::<$t>(ptr);
+            }
+            ::std::thread_local::KeyInner {
+                inner: ::std::cell::UnsafeCell { value: $init },
+                os: ::std::thread_local::OsStaticKey {
+                    inner: ::std::thread_local::OS_INIT_INNER,
+                    dtor: ::std::option::Some(__destroy),
+                },
+            }
+        };
+
+        INIT
+    });
+)
+
+impl<T: 'static> Key<T> {
+    /// Acquire a reference to the value in this TLS key.
+    ///
+    /// This will lazily initialize the value if this thread has not referenced
+    /// this key yet.
+    ///
+    /// # Panics
+    ///
+    /// This function will `panic!()` if the key currently has its
+    /// destructor running, and it **may** panic if the destructor has
+    /// previously been run for this thread.
+    pub fn with<R>(&'static self, f: |&T| -> R) -> R {
+        let slot = (self.inner)();
+        unsafe {
+            let slot = slot.get().expect("cannot access a TLS value during or \
+                                          after it is destroyed");
+            if (*slot.get()).is_none() {
+                *slot.get() = Some((self.init)());
+            }
+            f((*slot.get()).as_ref().unwrap())
+        }
+    }
+
+    /// Test this TLS key to determine whether its value has been destroyed for
+    /// the current thread or not.
+    ///
+    /// This will not initialize the key if it is not already initialized.
+    pub fn destroyed(&'static self) -> bool {
+        unsafe { (self.inner)().get().is_none() }
+    }
+}
+
+#[cfg(any(target_os = "macos", target_os = "linux"))]
+mod imp {
+    use prelude::*;
+
+    use cell::UnsafeCell;
+    use intrinsics;
+    use kinds::marker;
+    use ptr;
+
+    #[doc(hidden)]
+    pub struct Key<T> {
+        // Place the inner bits in an `UnsafeCell` to currently get around the
+        // "only Sync statics" restriction. This allows any type to be placed in
+        // the cell.
+        //
+        // Note that all access requires `T: 'static` so it can't be a type with
+        // any borrowed pointers still.
+        pub inner: UnsafeCell<T>,
+
+        // Metadata to keep track of the state of the destructor. Remember that
+        // these variables are thread-local, not global.
+        pub dtor_registered: UnsafeCell<bool>, // should be Cell
+        pub dtor_running: UnsafeCell<bool>, // should be Cell
+
+        // These shouldn't be copied around.
+        pub marker: marker::NoCopy,
+    }
+
+    #[doc(hidden)]
+    impl<T> Key<T> {
+        pub unsafe fn get(&'static self) -> Option<&'static T> {
+            if intrinsics::needs_drop::<T>() && *self.dtor_running.get() {
+                return None
+            }
+            self.register_dtor();
+            Some(&*self.inner.get())
+        }
+
+        unsafe fn register_dtor(&self) {
+            if !intrinsics::needs_drop::<T>() || *self.dtor_registered.get() {
+                return
+            }
+
+            register_dtor(self as *const _ as *mut u8,
+                          destroy_value::<T>);
+            *self.dtor_registered.get() = true;
+        }
+    }
+
+    // Since what appears to be glibc 2.18 this symbol has been shipped which
+    // GCC and clang both use to invoke destructors in thread_local globals, so
+    // let's do the same!
+    //
+    // Note, however, that we run on lots older linuxes, as well as cross
+    // compiling from a newer linux to an older linux, so we also have a
+    // fallback implementation to use as well.
+    //
+    // Due to rust-lang/rust#18804, make sure this is not generic!
+    #[cfg(target_os = "linux")]
+    unsafe fn register_dtor(t: *mut u8, dtor: unsafe extern fn(*mut u8)) {
+        use mem;
+        use libc;
+        use sys_common::thread_local as os;
+
+        extern {
+            static __dso_handle: *mut u8;
+            #[linkage = "extern_weak"]
+            static __cxa_thread_atexit_impl: *const ();
+        }
+        if !__cxa_thread_atexit_impl.is_null() {
+            type F = unsafe extern fn(dtor: unsafe extern fn(*mut u8),
+                                      arg: *mut u8,
+                                      dso_handle: *mut u8) -> libc::c_int;
+            mem::transmute::<*const (), F>(__cxa_thread_atexit_impl)
+            (dtor, t, __dso_handle);
+            return
+        }
+
+        // The fallback implementation uses a vanilla OS-based TLS key to track
+        // the list of destructors that need to be run for this thread. The key
+        // then has its own destructor which runs all the other destructors.
+        //
+        // The destructor for DTORS is a little special in that it has a `while`
+        // loop to continuously drain the list of registered destructors. It
+        // *should* be the case that this loop always terminates because we
+        // provide the guarantee that a TLS key cannot be set after it is
+        // flagged for destruction.
+        static DTORS: os::StaticKey = os::StaticKey {
+            inner: os::INIT_INNER,
+            dtor: Some(run_dtors),
+        };
+        type List = Vec<(*mut u8, unsafe extern fn(*mut u8))>;
+        if DTORS.get().is_null() {
+            let v: Box<List> = box Vec::new();
+            DTORS.set(mem::transmute(v));
+        }
+        let list: &mut List = &mut *(DTORS.get() as *mut List);
+        list.push((t, dtor));
+
+        unsafe extern fn run_dtors(mut ptr: *mut u8) {
+            while !ptr.is_null() {
+                let list: Box<List> = mem::transmute(ptr);
+                for &(ptr, dtor) in list.iter() {
+                    dtor(ptr);
+                }
+                ptr = DTORS.get();
+                DTORS.set(0 as *mut _);
+            }
+        }
+    }
+
+    // OSX's analog of the above linux function is this _tlv_atexit function.
+    // The disassembly of thread_local globals in C++ (at least produced by
+    // clang) will have this show up in the output.
+    #[cfg(target_os = "macos")]
+    unsafe fn register_dtor(t: *mut u8, dtor: unsafe extern fn(*mut u8)) {
+        extern {
+            fn _tlv_atexit(dtor: unsafe extern fn(*mut u8),
+                           arg: *mut u8);
+        }
+        _tlv_atexit(dtor, t);
+    }
+
+    #[doc(hidden)]
+    pub unsafe extern fn destroy_value<T>(ptr: *mut u8) {
+        let ptr = ptr as *mut Key<T>;
+        // Right before we run the user destructor be sure to flag the
+        // destructor as running for this thread so calls to `get` will return
+        // `None`.
+        *(*ptr).dtor_running.get() = true;
+        ptr::read((*ptr).inner.get() as *const T);
+    }
+}
+
+#[cfg(not(any(target_os = "macos", target_os = "linux")))]
+mod imp {
+    use prelude::*;
+
+    use cell::UnsafeCell;
+    use mem;
+    use sys_common::thread_local::StaticKey as OsStaticKey;
+
+    #[doc(hidden)]
+    pub struct Key<T> {
+        // Statically allocated initialization expression, using an `UnsafeCell`
+        // for the same reasons as above.
+        pub inner: UnsafeCell<T>,
+
+        // OS-TLS key that we'll use to key off.
+        pub os: OsStaticKey,
+    }
+
+    struct Value<T: 'static> {
+        key: &'static Key<T>,
+        value: T,
+    }
+
+    #[doc(hidden)]
+    impl<T> Key<T> {
+        pub unsafe fn get(&'static self) -> Option<&'static T> {
+            self.ptr().map(|p| &*p)
+        }
+
+        unsafe fn ptr(&'static self) -> Option<*mut T> {
+            let ptr = self.os.get() as *mut Value<T>;
+            if !ptr.is_null() {
+                if ptr as uint == 1 {
+                    return None
+                }
+                return Some(&mut (*ptr).value as *mut T);
+            }
+
+            // If the lookup returned null, we haven't initialized our own local
+            // copy, so do that now.
+            //
+            // Also note that this transmute_copy should be ok because the value
+            // `inner` is already validated to be a valid `static` value, so we
+            // should be able to freely copy the bits.
+            let ptr: Box<Value<T>> = box Value {
+                key: self,
+                value: mem::transmute_copy(&self.inner),
+            };
+            let ptr: *mut Value<T> = mem::transmute(ptr);
+            self.os.set(ptr as *mut u8);
+            Some(&mut (*ptr).value as *mut T)
+        }
+    }
+
+    #[doc(hidden)]
+    pub unsafe extern fn destroy_value<T: 'static>(ptr: *mut u8) {
+        // The OS TLS ensures that this key contains a NULL value when this
+        // destructor starts to run. We set it back to a sentinel value of 1 to
+        // ensure that any future calls to `get` for this thread will return
+        // `None`.
+        //
+        // Note that to prevent an infinite loop we reset it back to null right
+        // before we return from the destructor ourselves.
+        let ptr: Box<Value<T>> = mem::transmute(ptr);
+        let key = ptr.key;
+        key.os.set(1 as *mut u8);
+        drop(ptr);
+        key.os.set(0 as *mut u8);
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use prelude::*;
+
+    use cell::UnsafeCell;
+    use rustrt::thread::Thread;
+
+    struct Foo(Sender<()>);
+
+    impl Drop for Foo {
+        fn drop(&mut self) {
+            let Foo(ref s) = *self;
+            s.send(());
+        }
+    }
+
+    #[test]
+    fn smoke_no_dtor() {
+        thread_local!(static FOO: UnsafeCell<int> = UnsafeCell { value: 1 })
+
+        FOO.with(|f| unsafe {
+            assert_eq!(*f.get(), 1);
+            *f.get() = 2;
+        });
+        let (tx, rx) = channel();
+        spawn(proc() {
+            FOO.with(|f| unsafe {
+                assert_eq!(*f.get(), 1);
+            });
+            tx.send(());
+        });
+        rx.recv();
+
+        FOO.with(|f| unsafe {
+            assert_eq!(*f.get(), 2);
+        });
+    }
+
+    #[test]
+    fn smoke_dtor() {
+        thread_local!(static FOO: UnsafeCell<Option<Foo>> = UnsafeCell {
+            value: None
+        })
+
+        let (tx, rx) = channel();
+        spawn(proc() unsafe {
+            let mut tx = Some(tx);
+            FOO.with(|f| {
+                *f.get() = Some(Foo(tx.take().unwrap()));
+            });
+        });
+        rx.recv();
+    }
+
+    #[test]
+    fn circular() {
+        struct S1;
+        struct S2;
+        thread_local!(static K1: UnsafeCell<Option<S1>> = UnsafeCell {
+            value: None
+        })
+        thread_local!(static K2: UnsafeCell<Option<S2>> = UnsafeCell {
+            value: None
+        })
+        static mut HITS: uint = 0;
+
+        impl Drop for S1 {
+            fn drop(&mut self) {
+                unsafe {
+                    HITS += 1;
+                    if K2.destroyed() {
+                        assert_eq!(HITS, 3);
+                    } else {
+                        if HITS == 1 {
+                            K2.with(|s| *s.get() = Some(S2));
+                        } else {
+                            assert_eq!(HITS, 3);
+                        }
+                    }
+                }
+            }
+        }
+        impl Drop for S2 {
+            fn drop(&mut self) {
+                unsafe {
+                    HITS += 1;
+                    assert!(!K1.destroyed());
+                    assert_eq!(HITS, 2);
+                    K1.with(|s| *s.get() = Some(S1));
+                }
+            }
+        }
+
+        Thread::start(proc() {
+            drop(S1);
+        }).join();
+    }
+
+    #[test]
+    fn self_referential() {
+        struct S1;
+        thread_local!(static K1: UnsafeCell<Option<S1>> = UnsafeCell {
+            value: None
+        })
+
+        impl Drop for S1 {
+            fn drop(&mut self) {
+                assert!(K1.destroyed());
+            }
+        }
+
+        Thread::start(proc() unsafe {
+            K1.with(|s| *s.get() = Some(S1));
+        }).join();
+    }
+
+    #[test]
+    fn dtors_in_dtors_in_dtors() {
+        struct S1(Sender<()>);
+        thread_local!(static K1: UnsafeCell<Option<S1>> = UnsafeCell {
+            value: None
+        })
+        thread_local!(static K2: UnsafeCell<Option<Foo>> = UnsafeCell {
+            value: None
+        })
+
+        impl Drop for S1 {
+            fn drop(&mut self) {
+                let S1(ref tx) = *self;
+                unsafe {
+                    if !K2.destroyed() {
+                        K2.with(|s| *s.get() = Some(Foo(tx.clone())));
+                    }
+                }
+            }
+        }
+
+        let (tx, rx) = channel();
+        spawn(proc() unsafe {
+            let mut tx = Some(tx);
+            K1.with(|s| *s.get() = Some(S1(tx.take().unwrap())));
+        });
+        rx.recv();
+    }
+}
+
+#[cfg(test)]
+mod dynamic_tests {
+    use prelude::*;
+
+    use cell::RefCell;
+    use collections::HashMap;
+
+    #[test]
+    fn smoke() {
+        fn square(i: int) -> int { i * i }
+        thread_local!(static FOO: int = square(3))
+
+        FOO.with(|f| {
+            assert_eq!(*f, 9);
+        });
+    }
+
+    #[test]
+    fn hashmap() {
+        fn map() -> RefCell<HashMap<int, int>> {
+            let mut m = HashMap::new();
+            m.insert(1, 2);
+            RefCell::new(m)
+        }
+        thread_local!(static FOO: RefCell<HashMap<int, int>> = map())
+
+        FOO.with(|map| {
+            assert_eq!(map.borrow()[1], 2);
+        });
+    }
+
+    #[test]
+    fn refcell_vec() {
+        thread_local!(static FOO: RefCell<Vec<uint>> = RefCell::new(vec![1, 2, 3]))
+
+        FOO.with(|vec| {
+            assert_eq!(vec.borrow().len(), 3);
+            vec.borrow_mut().push(4);
+            assert_eq!(vec.borrow()[3], 4);
+        });
+    }
+}
diff --git a/src/libstd/thread_local/scoped.rs b/src/libstd/thread_local/scoped.rs
new file mode 100644
index 00000000000..11d539c4f9f
--- /dev/null
+++ b/src/libstd/thread_local/scoped.rs
@@ -0,0 +1,261 @@
+// Copyright 2014 The Rust Project Developers. See the COPYRIGHT
+// file at the top-level directory of this distribution and at
+// http://rust-lang.org/COPYRIGHT.
+//
+// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
+// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
+// option. This file may not be copied, modified, or distributed
+// except according to those terms.
+
+//! Scoped thread-local storage
+//!
+//! This module provides the ability to generate *scoped* thread-local
+//! variables. In this sense, scoped indicates that thread local storage
+//! actually stores a reference to a value, and this reference is only placed
+//! in storage for a scoped amount of time.
+//!
+//! There are no restrictions on what types can be placed into a scoped
+//! variable, but all scoped variables are initialized to the equivalent of
+//! null. Scoped thread local stor is useful when a value is present for a known
+//! period of time and it is not required to relinquish ownership of the
+//! contents.
+//!
+//! # Example
+//!
+//! ```
+//! scoped_thread_local!(static FOO: uint)
+//!
+//! // Initially each scoped slot is empty.
+//! assert!(!FOO.is_set());
+//!
+//! // When inserting a value, the value is only in place for the duration
+//! // of the closure specified.
+//! FOO.set(&1, || {
+//!     FOO.with(|slot| {
+//!         assert_eq!(*slot, 1);
+//!     });
+//! });
+//! ```
+
+#![macro_escape]
+
+use prelude::*;
+
+// macro hygiene sure would be nice, wouldn't it?
+#[doc(hidden)] pub use self::imp::KeyInner;
+#[doc(hidden)] pub use sys_common::thread_local::INIT as OS_INIT;
+
+/// Type representing a thread local storage key corresponding to a reference
+/// to the type parameter `T`.
+///
+/// Keys are statically allocated and can contain a reference to an instance of
+/// type `T` scoped to a particular lifetime. Keys provides two methods, `set`
+/// and `with`, both of which currently use closures to control the scope of
+/// their contents.
+pub struct Key<T> { #[doc(hidden)] pub inner: KeyInner<T> }
+
+/// Declare a new scoped thread local storage key.
+///
+/// This macro declares a `static` item on which methods are used to get and
+/// set the value stored within.
+#[macro_export]
+macro_rules! scoped_thread_local(
+    (static $name:ident: $t:ty) => (
+        __scoped_thread_local_inner!(static $name: $t)
+    );
+    (pub static $name:ident: $t:ty) => (
+        __scoped_thread_local_inner!(pub static $name: $t)
+    );
+)
+
+#[macro_export]
+#[doc(hidden)]
+macro_rules! __scoped_thread_local_inner(
+    (static $name:ident: $t:ty) => (
+        #[cfg_attr(not(any(windows, target_os = "android", target_os = "ios")),
+                   thread_local)]
+        static $name: ::std::thread_local::scoped::Key<$t> =
+            __scoped_thread_local_inner!($t);
+    );
+    (pub static $name:ident: $t:ty) => (
+        #[cfg_attr(not(any(windows, target_os = "android", target_os = "ios")),
+                   thread_local)]
+        pub static $name: ::std::thread_local::scoped::Key<$t> =
+            __scoped_thread_local_inner!($t);
+    );
+    ($t:ty) => ({
+        use std::thread_local::scoped::Key as __Key;
+
+        #[cfg(not(any(windows, target_os = "android", target_os = "ios")))]
+        const INIT: __Key<$t> = __Key {
+            inner: ::std::thread_local::scoped::KeyInner {
+                inner: ::std::cell::UnsafeCell { value: 0 as *mut _ },
+            }
+        };
+
+        #[cfg(any(windows, target_os = "android", target_os = "ios"))]
+        const INIT: __Key<$t> = __Key {
+            inner: ::std::thread_local::scoped::KeyInner {
+                inner: ::std::thread_local::scoped::OS_INIT,
+                marker: ::std::kinds::marker::InvariantType,
+            }
+        };
+
+        INIT
+    })
+)
+
+impl<T> Key<T> {
+    /// Insert a value into this scoped thread local storage slot for a
+    /// duration of a closure.
+    ///
+    /// While `cb` is running, the value `t` will be returned by `get` unless
+    /// this function is called recursively inside of `cb`.
+    ///
+    /// Upon return, this function will restore the previous value, if any
+    /// was available.
+    ///
+    /// # Example
+    ///
+    /// ```
+    /// scoped_thread_local!(static FOO: uint)
+    ///
+    /// FOO.set(&100, || {
+    ///     let val = FOO.with(|v| *v);
+    ///     assert_eq!(val, 100);
+    ///
+    ///     // set can be called recursively
+    ///     FOO.set(&101, || {
+    ///         // ...
+    ///     });
+    ///
+    ///     // Recursive calls restore the previous value.
+    ///     let val = FOO.with(|v| *v);
+    ///     assert_eq!(val, 100);
+    /// });
+    /// ```
+    pub fn set<R>(&'static self, t: &T, cb: || -> R) -> R {
+        struct Reset<'a, T: 'a> {
+            key: &'a KeyInner<T>,
+            val: *mut T,
+        }
+        #[unsafe_destructor]
+        impl<'a, T> Drop for Reset<'a, T> {
+            fn drop(&mut self) {
+                unsafe { self.key.set(self.val) }
+            }
+        }
+
+        let prev = unsafe {
+            let prev = self.inner.get();
+            self.inner.set(t as *const T as *mut T);
+            prev
+        };
+
+        let _reset = Reset { key: &self.inner, val: prev };
+        cb()
+    }
+
+    /// Get a value out of this scoped variable.
+    ///
+    /// This function takes a closure which receives the value of this
+    /// variable.
+    ///
+    /// # Panics
+    ///
+    /// This function will panic if `set` has not previously been called.
+    ///
+    /// # Example
+    ///
+    /// ```no_run
+    /// scoped_thread_local!(static FOO: uint)
+    ///
+    /// FOO.with(|slot| {
+    ///     // work with `slot`
+    /// });
+    /// ```
+    pub fn with<R>(&'static self, cb: |&T| -> R) -> R {
+        unsafe {
+            let ptr = self.inner.get();
+            assert!(!ptr.is_null(), "cannot access a scoped thread local \
+                                     variable without calling `set` first");
+            cb(&*ptr)
+        }
+    }
+
+    /// Test whether this TLS key has been `set` for the current thread.
+    pub fn is_set(&'static self) -> bool {
+        unsafe { !self.inner.get().is_null() }
+    }
+}
+
+#[cfg(not(any(windows, target_os = "android", target_os = "ios")))]
+mod imp {
+    use std::cell::UnsafeCell;
+
+    // FIXME: Should be a `Cell`, but that's not `Sync`
+    #[doc(hidden)]
+    pub struct KeyInner<T> { pub inner: UnsafeCell<*mut T> }
+
+    #[doc(hidden)]
+    impl<T> KeyInner<T> {
+        #[doc(hidden)]
+        pub unsafe fn set(&self, ptr: *mut T) { *self.inner.get() = ptr; }
+        #[doc(hidden)]
+        pub unsafe fn get(&self) -> *mut T { *self.inner.get() }
+    }
+}
+
+#[cfg(any(windows, target_os = "android", target_os = "ios"))]
+mod imp {
+    use kinds::marker;
+    use sys_common::thread_local::StaticKey as OsStaticKey;
+
+    #[doc(hidden)]
+    pub struct KeyInner<T> {
+        pub inner: OsStaticKey,
+        pub marker: marker::InvariantType<T>,
+    }
+
+    #[doc(hidden)]
+    impl<T> KeyInner<T> {
+        #[doc(hidden)]
+        pub unsafe fn set(&self, ptr: *mut T) { self.inner.set(ptr as *mut _) }
+        #[doc(hidden)]
+        pub unsafe fn get(&self) -> *mut T { self.inner.get() as *mut _ }
+    }
+}
+
+
+#[cfg(test)]
+mod tests {
+    use cell::Cell;
+    use prelude::*;
+
+    #[test]
+    fn smoke() {
+        scoped_thread_local!(static BAR: uint)
+
+        assert!(!BAR.is_set());
+        BAR.set(&1, || {
+            assert!(BAR.is_set());
+            BAR.with(|slot| {
+                assert_eq!(*slot, 1);
+            });
+        });
+        assert!(!BAR.is_set());
+    }
+
+    #[test]
+    fn cell_allowed() {
+        scoped_thread_local!(static BAR: Cell<uint>)
+
+        BAR.set(&Cell::new(1), || {
+            BAR.with(|slot| {
+                assert_eq!(slot.get(), 1);
+            });
+        });
+    }
+}
+