diff --git a/CHANGELOG.md b/CHANGELOG.md
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,17 @@
 # Revision history for nonempty-vector
 
+## 0.2.1.0
+
+* Added `consV` and `snocV` primitives for consing a vector to create a nonempty one. [(#8)](https://github.com/emilypi/nonempty-vector/pull/8) - Thanks @AlistairB!
+* Updated CI and cabal support
+* Expose constructors in new `Data.Vector.NonEmpty.Internal` module
+* Modules are now marked trustworthy
+* `@since` annotations have been added.
+
+## 0.2.0.2
+
+* Removed spurious dependency on `semigroups`
+
 ## 0.2.0.1
 
 * Missed a strictness tick in `postscanl'`
diff --git a/nonempty-vector.cabal b/nonempty-vector.cabal
--- a/nonempty-vector.cabal
+++ b/nonempty-vector.cabal
@@ -1,25 +1,29 @@
-cabal-version:      2.0
-name:               nonempty-vector
-version:            0.2.0.2
-synopsis:           Non-empty vectors
-description:
-  Performant non-empty mutable and immutable vectors.
-
-homepage:           https://github.com/emilypi/nonempty-vector
-bug-reports:        https://github.com/emilypi/nonempty-vector/issues
-license:            BSD3
-license-file:       LICENSE
-author:             Emily Pillmore
-maintainer:         emilypi@cohomolo.gy
-copyright:          (c) 2019 Emily Pillmore
-category:           Data
-build-type:         Custom
-extra-source-files:
+cabal-version:   1.24
+name:            nonempty-vector
+version:         0.2.1.0
+synopsis:        Non-empty vectors
+description:     Performant, non-empty mutable and immutable vectors
+homepage:        https://github.com/emilypi/nonempty-vector
+bug-reports:     https://github.com/emilypi/nonempty-vector/issues
+license:         BSD3
+license-file:    LICENSE
+author:          Emily Pillmore
+maintainer:      emilypi@cohomolo.gy
+copyright:       (c) 2019-2020 Emily Pillmore <emilypi@cohomolo.gy>
+category:        Data
+build-type:      Custom
+extra-doc-files:
   CHANGELOG.md
   README.md
 
 tested-with:
-  GHC ==8.2.2 || ==8.4.4 || ==8.6.5 || ==8.8.3 || ==8.10.1
+  GHC ==8.0.2
+   || ==8.4.3
+   || ==8.4.4
+   || ==8.6.3
+   || ==8.6.5
+   || ==8.8.1
+   || ==8.10.2
 
 source-repository head
   type:     git
@@ -34,13 +38,14 @@
 library
   exposed-modules:
     Data.Vector.NonEmpty
+    Data.Vector.NonEmpty.Internal
     Data.Vector.NonEmpty.Mutable
 
   build-depends:
-      base       >=4.10 && <5
+      base       >=4.9  && <5
     , deepseq
     , primitive  >=0.6  && <0.8
-    , vector     ^>=0.12
+    , vector     >=0.12 && <0.13
 
   hs-source-dirs:   src
   default-language: Haskell2010
@@ -51,7 +56,7 @@
   type:              exitcode-stdio-1.0
   main-is:           doctests.hs
   build-depends:
-      base     >=4.10 && <5
+      base     >=4.9 && <5
     , doctest
 
   hs-source-dirs:    test
diff --git a/src/Data/Vector/NonEmpty.hs b/src/Data/Vector/NonEmpty.hs
--- a/src/Data/Vector/NonEmpty.hs
+++ b/src/Data/Vector/NonEmpty.hs
@@ -1,11 +1,9 @@
-{-# LANGUAGE CPP #-}
-{-# LANGUAGE DeriveDataTypeable #-}
-{-# LANGUAGE GeneralizedNewtypeDeriving #-}
 {-# LANGUAGE NoImplicitPrelude #-}
 {-# LANGUAGE Rank2Types #-}
+{-# LANGUAGE Trustworthy #-}
 -- |
 -- Module       : Data.Vector.NonEmpty
--- Copyright 	: (c) 2019 Emily Pillmore
+-- Copyright 	: (c) 2019-2020 Emily Pillmore
 -- License	: BSD-style
 --
 -- Maintainer	: Emily Pillmore <emilypi@cohomolo.gy>
@@ -90,7 +88,7 @@
 , enumFromTo, enumFromThenTo
 
   -- ** Concatenation
-, cons, snoc, (++), concat, concat1
+, cons, consV, snoc, snocV, (++), concat, concat1
 
   -- ** Restricting memory usage
 , force
@@ -186,40 +184,29 @@
 ) where
 
 
-import Prelude ( Bool, Eq, Ord, Show(..), Num, Enum
+import Prelude ( Bool, Eq, Ord, Num, Enum
                , (.), Ordering, max, uncurry, snd)
 
-
-import Control.Applicative
-import Control.DeepSeq hiding (force)
-import Control.Monad (Monad, return)
+import Control.Monad (Monad)
 import Control.Monad.ST
-import Control.Monad.Zip (MonadZip)
 
-import Data.Data (Data)
-import Data.Foldable (Foldable)
 import qualified Data.Foldable as Foldable
 import Data.Functor
-import Data.Functor.Classes (Eq1, Ord1, Show1, Read1(..))
 import Data.Int
 import Data.List.NonEmpty (NonEmpty(..))
 import qualified Data.List.NonEmpty as NonEmpty
 import Data.Maybe (Maybe(..))
 import Data.Semigroup (Semigroup(..), (<>))
-import Data.Traversable (Traversable, traverse)
-import Data.Typeable (Typeable)
+import Data.Traversable (Traversable)
 import Data.Vector (Vector)
 import qualified Data.Vector as V
 import qualified Data.Vector.Generic as G
 import Data.Vector.Mutable (MVector)
-
-import GHC.Read
-
-import qualified Text.Read as Read
+import Data.Vector.NonEmpty.Internal
 
 
 -- $setup
--- >>> import Prelude (Int, String, ($), (.), (+), (<), const)
+-- >>> import Prelude (Int, String, ($), (.), (+), (<), const, return)
 -- >>> import Data.Bool
 -- >>> import Data.Eq
 -- >>> import qualified Prelude as P
@@ -229,57 +216,6 @@
 -- >>> :set -XTypeApplications
 -- >>> :set -XScopedTypeVariables
 
-
--- | 'NonEmptyVector' is a thin wrapper around 'Vector' that
--- witnesses an API requiring non-empty construction,
--- initialization, and generation of non-empty vectors by design.
---
--- A newtype wrapper was chosen so that no new pointer indirection
--- is introduced when working with 'Vector's, and all performance
--- characteristics inherited from the 'Vector' API still apply.
---
-newtype NonEmptyVector a = NonEmptyVector
-    { _neVec :: V.Vector a
-    } deriving
-      ( Eq, Ord
-      , Eq1, Ord1, Show1
-      , Data, Typeable, NFData
-      , Functor, Applicative, Monad
-      , MonadZip
-      , Semigroup
-      )
-
-instance Show a => Show (NonEmptyVector a) where
-    show (NonEmptyVector v) = show v
-
-instance Read a => Read (NonEmptyVector a) where
-    readPrec = do
-      as <- Read.readPrec
-      if Foldable.null as
-      then Read.pfail
-      else return (unsafeFromList as)
-
-instance Read1 NonEmptyVector where
-#if __GLASGOW_HASKELL__ > 802
-    liftReadPrec _ rl = do
-      l <- rl
-      if Foldable.null l
-      then Read.pfail
-      else return (unsafeFromList l)
-#else
-    liftReadsPrec _ r _ s = do
-      (as, s') <- r s
-      if Foldable.null as
-      then []
-      else return (unsafeFromList as, s')
-#endif
-
-instance Foldable NonEmptyVector where
-    foldMap f = Foldable.foldMap f . _neVec
-
-instance Traversable NonEmptyVector where
-    traverse f = fmap NonEmptyVector . traverse f . _neVec
-
 -- ---------------------------------------------------------------------- --
 -- Accessors + Indexing
 
@@ -1049,17 +985,35 @@
 -- [1,2,3]
 --
 cons :: a -> NonEmptyVector a -> NonEmptyVector a
-cons a (NonEmptyVector as) = NonEmptyVector (V.cons a as)
+cons a (NonEmptyVector as) = consV a as
 {-# INLINE cons #-}
 
+-- | /O(n)/ Prepend an element to a Vector
+--
+-- >>> consV 1 (V.fromList [2,3])
+-- [1,2,3]
+--
+consV :: a -> Vector a -> NonEmptyVector a
+consV a = NonEmptyVector . V.cons a
+{-# INLINE consV #-}
+
 -- | /O(n)/ Append an element
 --
 -- >>> snoc (unsafeFromList [1,2]) 3
 -- [1,2,3]
 --
 snoc :: NonEmptyVector a -> a -> NonEmptyVector a
-snoc (NonEmptyVector as) a = NonEmptyVector (V.snoc as a)
+snoc (NonEmptyVector as) = snocV as
 {-# INLINE snoc #-}
+
+-- | /O(n)/ Append an element to a Vector
+--
+-- >>> snocV (V.fromList [1,2]) 3
+-- [1,2,3]
+--
+snocV :: Vector a -> a -> NonEmptyVector a
+snocV as = NonEmptyVector . V.snoc as
+{-# INLINE snocV #-}
 
 -- | /O(m+n)/ Concatenate two non-empty vectors
 --
diff --git a/src/Data/Vector/NonEmpty/Internal.hs b/src/Data/Vector/NonEmpty/Internal.hs
new file mode 100644
--- /dev/null
+++ b/src/Data/Vector/NonEmpty/Internal.hs
@@ -0,0 +1,128 @@
+{-# LANGUAGE CPP #-}
+{-# LANGUAGE DeriveDataTypeable #-}
+{-# LANGUAGE GeneralizedNewtypeDeriving #-}
+{-# LANGUAGE LambdaCase #-}
+{-# LANGUAGE Trustworthy #-}
+-- |
+-- Module      : Data.Vector.NonEmpty.Internal
+-- Copyright   : (c) 2019-2020 Emily Pillmore
+-- License     : BSD-style
+--
+-- Maintainer  : Emily Pillmore <emilypi@cohomolo.gy>
+-- Stability   : experimental
+-- Portability : non-portable
+--
+-- Internal module exposing the constructors for
+-- 'NonEmptyVector' and 'NonEmptyMVector'.
+--
+-- /Warning/: Since the constructors are exposed here, by using this
+-- module, you take on the risks that you break the non-emptiness
+-- invariants of the main modules. Use at your own risk.
+--
+-- @since 0.2.1.0
+--
+module Data.Vector.NonEmpty.Internal
+( -- * Immutable boxed vectors
+  NonEmptyVector(..)
+  -- * Mutable boxed vectors
+, NonEmptyMVector(..)
+  -- ** Mutable vector aliases
+, NonEmptyIOVector
+, NonEmptySTVector
+) where
+
+
+import Control.DeepSeq (NFData)
+import Control.Monad.ST
+import Control.Monad.Zip (MonadZip)
+
+import Data.Data (Data)
+import qualified Data.Foldable as Foldable
+import Data.Functor.Classes (Eq1, Ord1, Show1, Read1(..))
+import qualified Data.Vector as V
+#if __GLASGOW_HASKELL__ < 804
+import Data.Semigroup (Semigroup(..))
+#endif
+import Data.Typeable (Typeable)
+import Data.Vector.Mutable (MVector)
+
+import qualified Text.Read as Read
+
+
+-- ---------------------------------------------------------------------- --
+-- Non-empty immutable vectors
+
+-- | 'NonEmptyVector' is a thin wrapper around 'Vector' that
+-- witnesses an API requiring non-empty construction,
+-- initialization, and generation of non-empty vectors by design.
+--
+-- A newtype wrapper was chosen so that no new pointer indirection
+-- is introduced when working with 'Vector's, and all performance
+-- characteristics inherited from the 'Vector' API still apply.
+--
+-- @since 0.2.1.0
+--
+newtype NonEmptyVector a = NonEmptyVector
+    { _neVec :: V.Vector a
+    } deriving
+      ( Eq, Ord
+      , Eq1, Ord1, Show1
+      , Data, Typeable, NFData
+      , Functor, Applicative, Monad
+      , MonadZip
+      , Semigroup
+      )
+
+instance Show a => Show (NonEmptyVector a) where
+    show (NonEmptyVector v) = show v
+
+instance Read a => Read (NonEmptyVector a) where
+    readPrec = Read.readPrec >>= \case
+      [] -> Read.pfail
+      as -> return (NonEmptyVector $ V.fromList as)
+
+instance Read1 NonEmptyVector where
+#if __GLASGOW_HASKELL__ > 802
+    liftReadPrec _ rl = rl >>= \case
+      [] -> Read.pfail
+      as -> return (NonEmptyVector $ V.fromList as)
+#else
+    liftReadsPrec _ r _ s = r s >>= \case
+      ([], _) -> []
+      (as, s') -> return (NonEmptyVector $ V.fromList as, s')
+#endif
+
+instance Foldable NonEmptyVector where
+    foldMap f = Foldable.foldMap f . _neVec
+
+instance Traversable NonEmptyVector where
+    traverse f = fmap NonEmptyVector . traverse f . _neVec
+
+-- ---------------------------------------------------------------------- --
+-- Non-empty mutable vectors
+
+-- | 'NonEmptyMVector' is a thin wrapper around 'MVector' that
+-- witnesses an API requiring non-empty construction,
+-- initialization, and generation of non-empty vectors by design.
+--
+-- A newtype wrapper was chosen so that no new pointer indirection
+-- is introduced when working with 'MVector's, and all performance
+-- characteristics inherited from the 'MVector' API still apply.
+--
+-- @since 0.2.1.0
+--
+newtype NonEmptyMVector s a = NonEmptyMVector
+    { _nemVec :: MVector s a }
+    deriving (Typeable)
+
+-- | 'NonEmptyMVector' parametrized by 'PrimState'
+--
+-- @since 0.2.1.0
+--
+type NonEmptyIOVector = NonEmptyMVector RealWorld
+
+-- | 'NonEmptyMVector' parametrized by 'ST'
+--
+-- @since 0.2.1.0
+--
+type NonEmptySTVector s = NonEmptyMVector s
diff --git a/src/Data/Vector/NonEmpty/Mutable.hs b/src/Data/Vector/NonEmpty/Mutable.hs
--- a/src/Data/Vector/NonEmpty/Mutable.hs
+++ b/src/Data/Vector/NonEmpty/Mutable.hs
@@ -1,9 +1,10 @@
 {-# LANGUAGE DeriveDataTypeable #-}
 {-# LANGUAGE BangPatterns #-}
 {-# LANGUAGE NoImplicitPrelude #-}
+{-# LANGUAGE Trustworthy #-}
 -- |
 -- Module      : Data.Vector.NonEmpty.Mutable
--- Copyright   : (c) 2019 Emily Pillmore
+-- Copyright   : (c) 2019-2020 Emily Pillmore
 -- License     : BSD-style
 --
 -- Maintainer  : Emily Pillmore <emilypi@cohomolo.gy>
@@ -62,28 +63,10 @@
 
 import Data.Functor
 import Data.Maybe (Maybe(..))
-import Data.Typeable (Typeable)
 import Data.Vector.Mutable (MVector)
 import qualified Data.Vector.Mutable as M
-
-
--- | 'NonEmptyMVector' is a thin wrapper around 'MVector' that
--- witnesses an API requiring non-empty construction,
--- initialization, and generation of non-empty vectors by design.
---
--- A newtype wrapper was chosen so that no new pointer indirection
--- is introduced when working with 'MVector's, and all performance
--- characteristics inherited from the 'MVector' API still apply.
---
-newtype NonEmptyMVector s a = NonEmptyMVector
-    { _nemVec :: MVector s a }
-    deriving (Typeable)
-
--- | 'NonEmptyMVector' parametrized by 'PrimState'
-type NonEmptyIOVector = NonEmptyMVector RealWorld
+import Data.Vector.NonEmpty.Internal
 
--- | 'NonEmptyMVector' parametrized by 'ST'
-type NonEmptySTVector s = NonEmptyMVector s
 
 -- ---------------------------------------------------------------------- --
 -- Length information
