diff --git a/CHANGELOG.md b/CHANGELOG.md
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,10 @@
 # Revision history for byteslice
 
-## 0.1.0.0 -- YYYY-mm-dd
+## 0.1.1.0 -- 2019-07-03
+
+* Add record labels for Bytes and MutableBytes
+* Add UnmanagedBytes. This is just an Addr and a length.
+
+## 0.1.0.0 -- 2019-04-30
 
 * First version.
diff --git a/byteslice.cabal b/byteslice.cabal
--- a/byteslice.cabal
+++ b/byteslice.cabal
@@ -1,8 +1,12 @@
 cabal-version: 2.2
 name: byteslice
-version: 0.1.0.0
-synopsis: Slicing ByteArray and MutableByteArray
--- description:
+version: 0.1.1.0
+synopsis: Slicing managed and unmanaged memory
+description:
+  This library provides types that allow the user to talk about a slice of
+  a ByteArray or a MutableByteArray. It also offers UnmanagedBytes, which
+  is kind of like a slice into unmanaged memory. However, it is just an
+  address and a length.
 homepage: https://github.com/andrewthad/byteslice
 bug-reports: https://github.com/andrewthad/byteslice/issues
 license: BSD-3-Clause
@@ -14,9 +18,11 @@
 extra-source-files: CHANGELOG.md
 
 library
-  exposed-modules: Data.Bytes.Types
+  exposed-modules:
+    Data.Bytes.Types
   build-depends:
     , base >=4.11.1.0 && <5
-    , primitive >=0.6.4 && <0.7
+    , primitive >=0.7 && <0.8
+    , primitive-addr >= 0.1 && <0.2
   hs-source-dirs: src
   default-language: Haskell2010
diff --git a/src/Data/Bytes/Types.hs b/src/Data/Bytes/Types.hs
--- a/src/Data/Bytes/Types.hs
+++ b/src/Data/Bytes/Types.hs
@@ -1,13 +1,17 @@
 {-# language BangPatterns #-}
 {-# language MagicHash #-}
 {-# language TypeFamilies #-}
+{-# language DuplicateRecordFields #-}
 
 module Data.Bytes.Types
   ( Bytes(..)
   , MutableBytes(..)
+  , UnmanagedBytes(..)
   ) where
 
+import Control.Monad.ST (runST)
 import Data.Primitive (ByteArray(..),MutableByteArray(..))
+import Data.Primitive.Addr (Addr)
 import Data.Bits ((.&.),unsafeShiftR)
 import Data.Char (ord)
 import Data.Word (Word8)
@@ -20,16 +24,24 @@
 
 -- | A slice of a 'ByteArray'.
 data Bytes = Bytes
-  {-# UNPACK #-} !ByteArray -- payload
-  {-# UNPACK #-} !Int -- offset
-  {-# UNPACK #-} !Int -- length
+  { array :: {-# UNPACK #-} !ByteArray
+  , offset :: {-# UNPACK #-} !Int
+  , length :: {-# UNPACK #-} !Int
+  }
 
 -- | A slice of a 'MutableByteArray'.
 data MutableBytes s = MutableBytes
-  {-# UNPACK #-} !(MutableByteArray s) -- payload
-  {-# UNPACK #-} !Int -- offset
-  {-# UNPACK #-} !Int -- length
+  { array :: {-# UNPACK #-} !(MutableByteArray s)
+  , offset :: {-# UNPACK #-} !Int
+  , length :: {-# UNPACK #-} !Int
+  }
 
+-- | A slice of unmanaged memory.
+data UnmanagedBytes = UnmanagedBytes
+  { address :: {-# UNPACK #-} !Addr
+  , length :: {-# UNPACK #-} !Int
+  }
+
 instance IsList Bytes where
   type Item Bytes = Word8
   fromListN n xs = Bytes (fromListN n xs) 0 n
@@ -75,6 +87,16 @@
   compare (Bytes arr1 off1 len1) (Bytes arr2 off2 len2)
     | sameByteArray arr1 arr2 && off1 == off2 && len1 == len2 = EQ
     | otherwise = compareByteArrays arr1 off1 arr2 off2 len1
+
+instance Semigroup Bytes where
+  -- TODO: Do the trick to move the data constructor to the outside
+  -- of runST.
+  Bytes arrA offA lenA <> Bytes arrB offB lenB = runST $ do
+    marr <- PM.newByteArray (lenA + lenB)
+    PM.copyByteArray marr 0 arrA offA lenA
+    PM.copyByteArray marr lenA arrB offB lenB
+    r <- PM.unsafeFreezeByteArray marr
+    pure (Bytes r 0 (lenA + lenB))
 
 compareByteArrays :: ByteArray -> Int -> ByteArray -> Int -> Int -> Ordering
 {-# INLINE compareByteArrays #-}
