Skip to content

Conversation

@facutuesca
Copy link
Contributor

Attempt at implementing custom error types for encoding, as per the comment in #562 (comment)

This PR:

  • Adds an associated type to the Asn1Writable, SimpleAsn1Writable and Asn1DefinedByWritable traits called Error, which specifies the error type for the associated write functions
  • Adds a helper attribute called error_type to the derive macro Asn1Write (which will populate the associated type mentioned above)
  • Changes the Asn1Write derive macro, adding more trait bounds to the derived SimpleAsn1Writable implementation for a type T.
    • The new trait bounds restrict the associated error types of each field of T to be convertible to T::Error. For example: T::Error: From<FieldOfT::Error>. This is so that an error that happens while writing any of the fields of T can be converted to the error type associated with T.

However, after adding these new bounds, the compiler now complains about the derived SimpleAsn1Writable implementation for T, for the cases where there are fields present maked as IMPLICIT and OPTIONAL.

error[E0277]: the trait bound `<T as test_perfect_derive::X>::Type: SimpleAsn1Writable` is not satisfied
   --> tests/derive_test.rs:898:30
    |
898 |     #[derive(asn1::Asn1Read, asn1::Asn1Write, PartialEq, Debug, Eq)]
    |                              ^^^^^^^^^^^^^^^ the trait `SimpleAsn1Writable` is not implemented for `<T as test_perfect_derive::X>::Type`
    |
    = note: required for `&'asn1_internal <T as test_perfect_derive::X>::Type` to implement `for<'asn1_internal> SimpleAsn1Writable`
    = note: this error originates in the derive macro `asn1::Asn1Write` (in Nightly builds, run with -Z macro-backtrace for more info)

when processing this type in derive_test:898:

    #[derive(asn1::Asn1Read, asn1::Asn1Write, PartialEq, Debug, Eq)]
    #[error_type(asn1::WriteError)]
    struct TaggedOptionalFields<T: X> {
        #[implicit(1)]
        a: Option<T::Type>,
        #[explicit(2)]
        b: Option<T::Type>,
    }

I don't quite understand why this is needed now (and not before), and trying to fix it by adding another bound so that X: SimpleAsn1Writable for the X in Implicit<X> didn't work.

I'm opening this as a draft for reference and discussion.

Signed-off-by: Facundo Tuesca <[email protected]>
@alex
Copy link
Owner

alex commented Jul 5, 2025

This is going to be related to these being "perfect" derives. If you use cargo expand you can see the code that's being generated for them, it might make what's going on clearer.

Signed-off-by: Facundo Tuesca <[email protected]>
@facutuesca
Copy link
Contributor Author

facutuesca commented Jul 5, 2025

Yeah, this is the expanded code for the trait impl that causes the error mentioned above:

    impl<T: X> asn1::SimpleAsn1Writable for TaggedRequiredFields<T>
    where
        for<'asn1_internal> asn1::Implicit<
            &'asn1_internal T::Type,
            1,
        >: asn1::Asn1Writable,
        for<'asn1_internal> asn1::Explicit<
            &'asn1_internal T::Type,
            2,
        >: asn1::Asn1Writable,
        asn1::WriteError: for<'asn1_internal> From<
            <asn1::Implicit<&'asn1_internal T::Type, 1> as asn1::Asn1Writable>::Error,
        >,
        asn1::WriteError: for<'asn1_internal> From<
            <asn1::Explicit<&'asn1_internal T::Type, 2> as asn1::Asn1Writable>::Error,
        >,
    {
        type Error = asn1::WriteError;
        const TAG: asn1::Tag = <asn1::SequenceWriter as asn1::SimpleAsn1Writable>::TAG;
        fn write_data(&self, dest: &mut asn1::WriteBuf) -> Result<(), Self::Error> {
            let mut w = asn1::Writer::new(dest);
            w.write_element(&asn1::Implicit::<_, 1>::new(&self.a))?;
            w.write_element(&asn1::Explicit::<_, 2>::new(&self.b))?;
            Ok(())
        }
        fn data_length(&self) -> Option<usize> {
            Some(
                0
                    + asn1::Asn1Writable::encoded_length(
                        &asn1::Implicit::<_, 1>::new(&self.a),
                    )?
                    + asn1::Asn1Writable::encoded_length(
                        &asn1::Explicit::<_, 2>::new(&self.b),
                    )?,
            )
        }
    }

The things that changed are the new trait bounds for the error types, and adding the new error associated type:

        asn1::WriteError: for<'asn1_internal> From<
            <asn1::Implicit<&'asn1_internal T::Type, 1> as asn1::Asn1Writable>::Error,
        >,
        asn1::WriteError: for<'asn1_internal> From<
            <asn1::Explicit<&'asn1_internal T::Type, 2> as asn1::Asn1Writable>::Error,
        >,
/......
        type Error = asn1::WriteError;
        fn write_data(&self, dest: &mut asn1::WriteBuf) -> Result<(), Self::Error> {

But as far as I understand, these new bounds should not have any new requirements, since the Implicit/Explicit types mentioned are already constrained to be Asn1Writable by the existing bounds.

@alex
Copy link
Owner

alex commented Jul 5, 2025 via email

Signed-off-by: Facundo Tuesca <[email protected]>
@facutuesca
Copy link
Contributor Author

Hmm, I don't think you want asn1::WriteError on the LHS of a bound, I think you want to reformulate it with the type on the LHS and RHS being Into<WriteError> (which should work because there's always symmetric into/from impls)

I pushed the change, but the error is still the same, plus now the ? operator doesn't work since it depends on the From trait (and implementing Into doesn't automatically implement From)

@alex
Copy link
Owner

alex commented Aug 30, 2025

Hmm, what's the status of this, looks like we haven't quite been able to get it building correctly? Do we have a minimized example of the pattern that doesn't build?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants