diff --git a/proto3-wire.cabal b/proto3-wire.cabal
--- a/proto3-wire.cabal
+++ b/proto3-wire.cabal
@@ -1,5 +1,5 @@
 name:                proto3-wire
-version:             1.0.0
+version:             1.1.0
 synopsis:            A low-level implementation of the Protocol Buffers (version 3) wire format
 license:             Apache-2.0
 license-file:        LICENSE
@@ -13,6 +13,7 @@
 library
   exposed-modules:     Proto3.Wire
                        Proto3.Wire.Builder
+                       Proto3.Wire.Class
                        Proto3.Wire.Decode
                        Proto3.Wire.Encode
                        Proto3.Wire.Tutorial
diff --git a/src/Proto3/Wire.hs b/src/Proto3/Wire.hs
--- a/src/Proto3/Wire.hs
+++ b/src/Proto3/Wire.hs
@@ -17,8 +17,10 @@
 -- | See the "Proto3.Wire.Tutorial" module.
 
 module Proto3.Wire
-    ( -- * Message Structure
-      FieldNumber(..)
+    ( -- * Support Classes
+      ProtoEnum(..)
+      -- * Message Structure
+    , FieldNumber(..)
     , fieldNumber
       -- * Decoding Messages
     , at
@@ -27,5 +29,6 @@
     , repeated
     ) where
 
+import           Proto3.Wire.Class
 import           Proto3.Wire.Types
 import           Proto3.Wire.Decode
diff --git a/src/Proto3/Wire/Class.hs b/src/Proto3/Wire/Class.hs
new file mode 100644
--- /dev/null
+++ b/src/Proto3/Wire/Class.hs
@@ -0,0 +1,44 @@
+{-# LANGUAGE DefaultSignatures #-}
+{-
+  Copyright 2019 Awake Networks
+
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+      http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+-}
+
+-- | This module defines classes which are shared by the encoding and decoding
+-- modules.
+
+module Proto3.Wire.Class
+  ( ProtoEnum(..)
+  ) where
+
+import           Data.Int (Int32)
+import qualified Safe
+
+-- | Similar to 'Enum', but allows gaps in the sequence of numeric codes,
+-- and uses 'Int32' in order to match the proto3 specification.
+--
+-- Absent gaps, you can use an automatic derivation of 'Bounded' and 'Enum',
+-- then use the default implementations for all methods of this class.  But
+-- if gaps are involved, then you must instantiate this class directly and
+-- supply the specific numeric codes desired for each constructor.
+class ProtoEnum a where
+    -- | Default implementation: `Safe.toEnumMay`.
+    toProtoEnumMay :: Int32 -> Maybe a
+    default toProtoEnumMay :: (Bounded a, Enum a) => Int32 -> Maybe a
+    toProtoEnumMay = Safe.toEnumMay . fromIntegral
+
+    -- | Default implementation: 'fromEnum'.
+    fromProtoEnum :: a -> Int32
+    default fromProtoEnum :: Enum a => a -> Int32
+    fromProtoEnum = fromIntegral . fromEnum
diff --git a/src/Proto3/Wire/Decode.hs b/src/Proto3/Wire/Decode.hs
--- a/src/Proto3/Wire/Decode.hs
+++ b/src/Proto3/Wire/Decode.hs
@@ -94,8 +94,8 @@
 import qualified Data.Traversable        as T
 import           Data.Int                ( Int32, Int64 )
 import           Data.Word               ( Word8, Word32, Word64 )
+import           Proto3.Wire.Class
 import           Proto3.Wire.Types
-import qualified Safe
 
 -- | Decode a zigzag-encoded numeric type.
 -- See: http://stackoverflow.com/questions/2210923/zig-zag-decoding
@@ -348,7 +348,10 @@
 
 -- | Parse a Boolean value.
 bool :: Parser RawPrimitive Bool
-bool = fmap (Safe.toEnumDef False) parseVarInt
+bool = Parser $
+    \case
+        VarintField i -> return $! i /= 0
+        wrong -> throwWireTypeError "bool" wrong
 
 -- | Parse a primitive with the @int32@ wire type.
 int32 :: Parser RawPrimitive Int32
@@ -395,14 +398,14 @@
 
 -- | Parse a primitive with an enumerated type.
 --
--- This parser will return 'Left' if the encoded integer value is outside the
--- acceptable range of the 'Bounded' instance.
-enum :: forall e. (Enum e, Bounded e) => Parser RawPrimitive (Either Int e)
+-- This parser will return 'Left' if the encoded integer value
+-- is not a code for a known enumerator.
+enum :: forall e. ProtoEnum e => Parser RawPrimitive (Either Int32 e)
 enum = fmap toEither parseVarInt
   where
-    toEither :: Int -> Either Int e
+    toEither :: Int32 -> Either Int32 e
     toEither i
-      | Just e <- Safe.toEnumMay i = Right e
+      | Just e <- toProtoEnumMay i = Right e
       | otherwise = Left i
 
 -- | Parse a packed collection of variable-width integer values (any of @int32@,
diff --git a/src/Proto3/Wire/Encode.hs b/src/Proto3/Wire/Encode.hs
--- a/src/Proto3/Wire/Encode.hs
+++ b/src/Proto3/Wire/Encode.hs
@@ -66,6 +66,7 @@
     , float
     , double
     , enum
+    , bool
       -- * Strings
     , bytes
     , string
@@ -94,6 +95,7 @@
 import qualified Data.Text.Lazy.Encoding       as Text.Lazy.Encoding
 import           Data.Word                     ( Word8, Word32, Word64 )
 import qualified Proto3.Wire.Builder           as WB
+import           Proto3.Wire.Class
 import           Proto3.Wire.Types
 
 -- $setup
@@ -265,16 +267,39 @@
 
 -- | Encode a value with an enumerable type.
 --
--- It can be useful to derive an 'Enum' instance for a type in order to
--- emulate enums appearing in .proto files.
+-- You should instantiate 'ProtoEnum' for a type in
+-- order to emulate enums appearing in .proto files.
 --
 -- For example:
 --
--- >>> data Shape = Circle | Square | Triangle deriving (Enum)
--- >>> 1 `enum` True <> 2 `enum` Circle
--- Proto3.Wire.Encode.unsafeFromLazyByteString "\b\SOH\DLE\NUL"
-enum :: Enum e => FieldNumber -> e -> MessageBuilder
-enum num e = fieldHeader num Varint <> base128Varint (fromIntegral (fromEnum e))
+-- >>> :{
+--     data Shape = Circle | Square | Triangle deriving (Bounded, Enum)
+--     instance ProtoEnum Shape
+--     data Gap = Gap0 | Gap3
+--     instance ProtoEnum Gap where
+--       toProtoEnumMay i = case i of
+--         0 -> Just Gap0
+--         3 -> Just Gap3
+--         _ -> Nothing
+--       fromProtoEnum g = case g of
+--         Gap0 -> 0
+--         Gap3 -> 3
+-- :}
+--
+-- >>> 1 `enum` Triangle <> 2 `enum` Gap3
+-- Proto3.Wire.Encode.unsafeFromLazyByteString "\b\STX\DLE\ETX"
+enum :: ProtoEnum e => FieldNumber -> e -> MessageBuilder
+enum num e =
+  fieldHeader num Varint <> base128Varint (fromIntegral (fromProtoEnum e))
+
+-- | Encode a boolean value
+--
+-- For example:
+--
+-- >>> 1 `bool` True
+-- Proto3.Wire.Encode.unsafeFromLazyByteString "\b\SOH"
+bool :: FieldNumber -> Bool -> MessageBuilder
+bool num i = fieldHeader num Varint <> base128Varint (fromIntegral (fromEnum i))
 
 -- | Encode a sequence of octets as a field of type 'bytes'.
 --
diff --git a/test/Main.hs b/test/Main.hs
--- a/test/Main.hs
+++ b/test/Main.hs
@@ -109,7 +109,7 @@
                                        (Encode.double (fieldNumber 1))
                                        (one Decode.double 0 `at` fieldNumber 1)
                            , roundTrip "bool"
-                                       (Encode.enum (fieldNumber 1))
+                                       (Encode.bool (fieldNumber 1))
                                        (one Decode.bool False `at` fieldNumber 1)
                            , roundTrip "text"
                                        (Encode.text (fieldNumber 1) . T.pack)
