diff --git a/src/conversion.rs b/src/conversion.rs
index ae703213346..5ec8a8e0edc 100644
--- a/src/conversion.rs
+++ b/src/conversion.rs
@@ -134,10 +134,73 @@ where
     }
 }
 
-/// Similar to [std::convert::Into], just that it requires a gil token.
+/// Defines a conversion from a Rust type to a Python object.
 ///
-/// `IntoPy<PyObject>` (aka `IntoPy<Py<PyAny>>`) should be implemented to define a conversion from
-/// Rust to Python which can be used by most of PyO3's methods.
+/// It functions similarly to std's [`Into`](std::convert::Into) trait,
+/// but requires a [GIL token](Python) as an argument.
+/// Many functions and traits internal to PyO3 require this trait as a bound,
+/// so a lack of this trait can manifest itself in different error messages.
+///
+/// # Examples
+/// ## With `#[pyclass]`
+/// The easiest way to implement `IntoPy` is by exposing a struct as a native Python object
+/// by annotating it with [`#[pyclass]`](crate::prelude::pyclass).
+///
+/// ```rust
+/// use pyo3::prelude::*;
+///
+/// #[pyclass]
+/// struct Number {
+///    #[pyo3(get, set)]
+///    value: i32,
+/// }
+/// ```
+/// Python code will see this as an instance of the `Number` class with a `value` attribute.
+///
+/// ## Conversion to a Python object
+///
+/// However, it may not be desirable to expose the existence of `Number` to Python code.
+/// `IntoPy` allows us to define a conversion to an appropriate Python object.
+/// ```rust
+/// use pyo3::prelude::*;
+///
+/// struct Number {
+///   value: i32,
+/// }
+///
+/// impl IntoPy<PyObject> for Number {
+///     fn into_py(self, py: Python) -> PyObject {
+///         // delegates to i32's IntoPy implementation.
+///         self.value.into_py(py)
+///    }
+/// }
+/// ```
+/// Python code will see this as an `int` object.
+///
+/// ## Dynamic conversion into Python objects.
+/// It is also possible to return a different Python object depending on some condition.
+/// This is useful for types like enums that can carry different types.
+///
+/// ```rust
+/// use pyo3::prelude::*;
+///
+/// enum Value {
+///     Integer(i32),
+///     String(String),
+///     None
+/// }
+///
+/// impl IntoPy<PyObject> for Value {
+///     fn into_py(self, py: Python) -> PyObject {
+///         match self {
+///             Self::Integer(val) => val.into_py(py),
+///             Self::String(val) => val.into_py(py),
+///             Self::None => py.None()
+///         }
+///    }
+/// }
+/// ```
+/// Python code will see this as any of the `int`, `string` or `None` objects.
 pub trait IntoPy<T>: Sized {
     /// Performs the conversion.
     fn into_py(self, py: Python) -> T;