diff --git a/CHANGELOG.md b/CHANGELOG.md
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,10 @@
 # Revision history for byteslice
 
+## 0.1.2.0 -- 2019-08-21
+
+* Add `Data.Bytes.Mutable` module.
+* Add `Data.Bytes` module.
+
 ## 0.1.1.0 -- 2019-07-03
 
 * Add record labels for Bytes and MutableBytes
diff --git a/byteslice.cabal b/byteslice.cabal
--- a/byteslice.cabal
+++ b/byteslice.cabal
@@ -1,6 +1,6 @@
 cabal-version: 2.2
 name: byteslice
-version: 0.1.1.0
+version: 0.1.2.0
 synopsis: Slicing managed and unmanaged memory
 description:
   This library provides types that allow the user to talk about a slice of
@@ -19,10 +19,14 @@
 
 library
   exposed-modules:
+    Data.Bytes
+    Data.Bytes.Mutable
     Data.Bytes.Types
   build-depends:
     , base >=4.11.1.0 && <5
     , primitive >=0.7 && <0.8
-    , primitive-addr >= 0.1 && <0.2
+    , primitive-addr >=0.1 && <0.2
+    , run-st >=0.1 && <0.2
   hs-source-dirs: src
+  ghc-options: -Wall -O2
   default-language: Haskell2010
diff --git a/src/Data/Bytes.hs b/src/Data/Bytes.hs
new file mode 100644
--- /dev/null
+++ b/src/Data/Bytes.hs
@@ -0,0 +1,103 @@
+{-# language BangPatterns #-}
+{-# language TypeApplications #-}
+
+module Data.Bytes
+  ( -- * Types
+    Bytes
+    -- * Properties
+  , null
+  , length
+    -- * Filtering
+  , takeWhile
+  , dropWhile
+    -- * Unsafe Slicing
+  , unsafeTake
+  , unsafeDrop
+    -- * Conversion
+  , toByteArray
+  , toByteArrayClone
+  , fromAsciiString
+  , fromByteArray
+  ) where
+
+import Prelude hiding (length,takeWhile,dropWhile,null)
+
+import Data.Bytes.Types (Bytes(Bytes))
+import Data.Primitive (ByteArray)
+import Data.Word (Word8)
+import Data.Char (ord)
+import Control.Monad.ST.Run (runByteArrayST)
+
+import qualified Data.Primitive as PM
+import qualified GHC.Exts as Exts
+
+-- | Is the byte sequence empty?
+null :: Bytes -> Bool
+null (Bytes _ _ len) = len == 0
+
+-- | The length of a slice of bytes.
+length :: Bytes -> Int
+length (Bytes _ _ len) = len
+
+-- | Take bytes while the predicate is true.
+takeWhile :: (Word8 -> Bool) -> Bytes -> Bytes
+{-# inline takeWhile #-}
+takeWhile k b = unsafeTake (countWhile k b) b
+
+-- | Drop bytes while the predicate is true.
+dropWhile :: (Word8 -> Bool) -> Bytes -> Bytes
+{-# inline dropWhile #-}
+dropWhile k b = unsafeDrop (countWhile k b) b
+
+-- | Take the first @n@ bytes from the argument. Precondition: @n ≤ len@
+unsafeTake :: Int -> Bytes -> Bytes
+{-# inline unsafeTake #-}
+unsafeTake n (Bytes arr off _) =
+  Bytes arr off n
+
+-- | Drop the first @n@ bytes from the argument. Precondition: @n ≤ len@
+unsafeDrop :: Int -> Bytes -> Bytes
+{-# inline unsafeDrop #-}
+unsafeDrop n (Bytes arr off len) =
+  Bytes arr (off + n) (len - n)
+
+-- Internal. The returns the number of bytes that match the
+-- predicate until the first non-match occurs. If all bytes
+-- match the predicate, this will return the length originally
+-- provided.
+countWhile :: (Word8 -> Bool) -> Bytes -> Int
+{-# inline countWhile #-}
+countWhile k (Bytes arr off0 len0) = go off0 len0 0 where
+  go !off !len !n = if len > 0
+    then if k (PM.indexByteArray arr off)
+      then go (off + 1) (len - 1) (n + 1)
+      else n
+    else n
+
+-- | Convert the sliced 'Bytes' to an unsliced 'ByteArray'. This
+-- reuses the array backing the sliced 'Bytes' if the slicing metadata
+-- implies that all of the bytes are used. Otherwise, it makes a copy.
+toByteArray :: Bytes -> ByteArray
+toByteArray b@(Bytes arr off len)
+  | off /= 0 = toByteArrayClone b
+  | PM.sizeofByteArray arr /= len = toByteArrayClone b
+  | otherwise = arr
+
+-- | Variant of 'toByteArray' that unconditionally makes a copy of
+-- the array backing the sliced 'Bytes' even if the original array
+-- could be reused. Prefer 'toByteArray'.
+toByteArrayClone :: Bytes -> ByteArray
+toByteArrayClone (Bytes arr off len) = runByteArrayST $ do
+  m <- PM.newByteArray len
+  PM.copyByteArray m 0 arr off len
+  PM.unsafeFreezeByteArray m
+
+-- | Convert a 'String' consisting of only characters
+--   in the ASCII block.
+fromAsciiString :: String -> Bytes
+fromAsciiString = fromByteArray . Exts.fromList . map (fromIntegral @Int @Word8 . ord)
+
+-- | Create a slice of 'Bytes' that spans the entire argument array.
+fromByteArray :: ByteArray -> Bytes
+fromByteArray b = Bytes b 0 (PM.sizeofByteArray b)
+
diff --git a/src/Data/Bytes/Mutable.hs b/src/Data/Bytes/Mutable.hs
new file mode 100644
--- /dev/null
+++ b/src/Data/Bytes/Mutable.hs
@@ -0,0 +1,85 @@
+{-# language BangPatterns #-}
+{-# language LambdaCase #-}
+
+module Data.Bytes.Mutable
+  ( -- * Types
+    MutableBytes
+    -- * Filtering
+  , takeWhile
+  , dropWhile
+    -- * Unsafe Slicing
+  , unsafeTake
+  , unsafeDrop
+    -- * Conversion
+  , fromMutableByteArray
+  ) where
+
+import Prelude hiding (takeWhile,dropWhile)
+
+import Data.Bytes.Types (MutableBytes(MutableBytes))
+import Data.Primitive (MutableByteArray)
+import Data.Word (Word8)
+import Control.Monad.Primitive (PrimMonad,PrimState)
+
+import qualified Data.Primitive as PM
+
+-- | Take bytes while the predicate is true, aliasing the
+-- argument array.
+takeWhile :: PrimMonad m
+  => (Word8 -> m Bool)
+  -> MutableBytes (PrimState m)
+  -> m (MutableBytes (PrimState m))
+{-# inline takeWhile #-}
+takeWhile k b = do
+  n <- countWhile k b
+  pure (unsafeTake n b)
+
+-- | Drop bytes while the predicate is true, aliasing the
+-- argument array.
+dropWhile :: PrimMonad m
+  => (Word8 -> m Bool)
+  -> MutableBytes (PrimState m)
+  -> m (MutableBytes (PrimState m))
+{-# inline dropWhile #-}
+dropWhile k b = do
+  n <- countWhile k b
+  pure (unsafeDrop n b)
+
+-- | Take the first @n@ bytes from the argument, aliasing it.
+unsafeTake :: Int -> MutableBytes s -> MutableBytes s
+{-# inline unsafeTake #-}
+unsafeTake n (MutableBytes arr off _) =
+  MutableBytes arr off n
+
+-- | Drop the first @n@ bytes from the argument, aliasing it.
+-- The new length will be @len - n@.
+unsafeDrop :: Int -> MutableBytes s -> MutableBytes s
+{-# inline unsafeDrop #-}
+unsafeDrop n (MutableBytes arr off len) =
+  MutableBytes arr (off + n) (len - n)
+
+-- | Create a slice of 'MutableBytes' that spans the entire
+-- argument array. This aliases the argument.
+fromMutableByteArray :: PrimMonad m
+  => MutableByteArray (PrimState m)
+  -> m (MutableBytes (PrimState m))
+{-# inline fromMutableByteArray #-}
+fromMutableByteArray mba = do
+  sz <- PM.getSizeofMutableByteArray mba
+  pure (MutableBytes mba 0 sz)
+
+-- Internal. The returns the number of bytes that match the
+-- predicate until the first non-match occurs. If all bytes
+-- match the predicate, this will return the length originally
+-- provided.
+countWhile :: PrimMonad m
+  => (Word8 -> m Bool)
+  -> MutableBytes (PrimState m)
+  -> m Int
+{-# inline countWhile #-}
+countWhile k (MutableBytes arr off0 len0) = go off0 len0 0 where
+  go !off !len !n = if len > 0
+    then (k =<< PM.readByteArray arr off) >>= \case
+      True -> go (off + 1) (len - 1) (n + 1)
+      False -> pure n
+    else pure n
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
@@ -54,7 +54,7 @@
   else []
 
 instance Show Bytes where
-  showsPrec d (Bytes arr off len) s = if len == 0
+  showsPrec _ (Bytes arr off len) s = if len == 0
     then showString "[]" s
     else showString "[0x"
        $ showHexDigits (PM.indexByteArray arr off)
