Skip to content

util: add default_value_vec for defaults without LengthReadable#4507

Draft
carlaKC wants to merge 1 commit intolightningdevkit:mainfrom
carlaKC:default-vec-macro
Draft

util: add default_value_vec for defaults without LengthReadable#4507
carlaKC wants to merge 1 commit intolightningdevkit:mainfrom
carlaKC:default-vec-macro

Conversation

@carlaKC
Copy link
Contributor

@carlaKC carlaKC commented Mar 23, 2026

⚠️ Clauded and did not review this - just looking for a concept ack and I"ll clean it up properly !


In lightningdevkit/ldk-node#825, I ran into the orphan rule when trying to migrate from a single incoming/outgoing HTLC to a vec of HTLCLocator types to allow expressing multiple in/out HTLCs. This issue stems from the following:

  • We want to declare our own HTLCLocator type in LDK-Node, so that we can have niceties like a typed user_channel_id.
  • We don't want to break out of the impl_writeable_tlv_based_enum! macro for LDK-Node's Event type.
  • To be forwards compatible (use the legacy single-htlc fields), we should use default_value to fill the old fields when the new ones aren't present.

However:

  • default_value uses required under the hood, which expects the field to be LengthReadable.
  • We cannot implement LengthReadable for Vec<HTLCLocator> in LDK-Node due to the orphan rule (we don't own Vec<T>, even if T is our type).
    • In LDK, this is when we use impl_for_vec! to implement this.

This change introduces a new default_value_vec to our macro which uses required_vec (and thus WithoutLength) under the hood, so that we don't need LengthReadable. There are a few ugly workarounds (like using custom), but wanting to write a Vec with a default seems like a common enough one to justify a change to our macros.

Right now, use of `default_value` requires that the struct implements
`LengthReadable` itself. When trying to use `default_value` outside of
LDK for `Vec<T>`, your code will run into the orphan rule because it
does not own the trait `LengthReadable` or the type `Vec`.

There are various ugly workarounds for this (like using `custom`), but
wanting to persist a vec with a default value seems like a common enough
use case to justify the change.
@ldk-reviews-bot
Copy link

👋 Hi! I see this is a draft PR.
I'll wait to assign reviewers until you mark it as ready for review.
Just convert it out of draft status when you're ready for review!

@codecov
Copy link

codecov bot commented Mar 23, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 86.20%. Comparing base (1f0763f) to head (bcde6b5).

Additional details and impacted files
@@            Coverage Diff             @@
##             main    #4507      +/-   ##
==========================================
+ Coverage   86.18%   86.20%   +0.01%     
==========================================
  Files         160      160              
  Lines      107537   107558      +21     
  Branches   107537   107558      +21     
==========================================
+ Hits        92681    92720      +39     
+ Misses      12229    12212      -17     
+ Partials     2627     2626       -1     
Flag Coverage Δ
tests 86.20% <100.00%> (+0.01%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

items: Vec<u32>,
}
impl_writeable_tlv_based!(DefaultValueVecStruct, {
(1, items, (default_value_vec, Vec::new())),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: Might make sense to use a dummy default value here rather than the empty Vec to check this is actually what's used rather than Vec::default() in general?

@TheBlueMatt
Copy link
Collaborator

default_value uses required under the hood, which expects the field to be LengthReadable.
We cannot implement LengthReadable for Vec in LDK-Node due to the orphan rule (we don't own Vec, even if T is our type).
In LDK, this is when we use impl_for_vec! to implement this.

Hmm, I guess we could use custom for this, no? In both impls wrap with WithoutLength and call it a day?

I'm not a huge fan of yet more write variants, ideally we'd drop some, really, but I do see why this is useful, and its not much of an extension on what we already have, so if custom isn't super clean I'm fine with this.

@carlaKC
Copy link
Contributor Author

carlaKC commented Mar 24, 2026

Hmm, I guess we could use custom for this, no? In both impls wrap with WithoutLength and call it a day?

Yeah we can, it's just pretty ugly, worth having the option IMO.

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.

4 participants