Socket Abstraction Layer Devirtualized
The socket::Socket and socket::ListenSocket types have been changed from virtual base classes to concrete type aliases. Listen sockets now use the ListenSocket type instead of Socket. These changes save 1,020–3,228 bytes of flash across platforms by eliminating virtual dispatch overhead.
This is a breaking change for external components in ESPHome 2026.3.0 and later.
Background
Section titled “Background”PR #14398: Devirtualize socket abstraction layer
ESPHome’s socket abstraction previously used virtual base classes (socket::Socket) even though only one socket implementation is active per build (selected at compile time via #ifdef). Since there’s no runtime polymorphism needed, the virtual dispatch was pure overhead — each socket method call went through a vtable indirection, and the compiler couldn’t inline the implementations.
By converting to concrete type aliases, the compiler can fully inline socket methods (read/write/writev) into callers, eliminating vtable overhead on the hot path.
Flash savings
Section titled “Flash savings”| Platform | Savings |
|---|---|
| ESP32 | 3,228 bytes (0.21%) |
| ESP8266 | 1,020 bytes (0.25%) |
| RP2040 | 2,836 bytes (0.71%) |
What’s Changing
Section titled “What’s Changing”Type changes
Section titled “Type changes”socket::Socket and socket::ListenSocket are now type aliases for the platform-specific concrete implementation instead of virtual base classes.
Listen sockets returned by socket_ip_loop_monitored() and similar factory functions now return std::unique_ptr<ListenSocket> instead of std::unique_ptr<Socket>.
Who This Affects
Section titled “Who This Affects”External components that:
- Create TCP server sockets using
socket::socket_ip_loop_monitored()or similar factory functions and store them asstd::unique_ptr<socket::Socket>
Standard YAML configurations are not affected.
Migration Guide
Section titled “Migration Guide”Listen socket type
Section titled “Listen socket type”// Beforestd::unique_ptr<socket::Socket> server_;
void setup() override { server_ = socket::socket_ip_loop_monitored(SOCK_STREAM, 0);}
// Afterstd::unique_ptr<socket::ListenSocket> server_;
void setup() override { server_ = socket::socket_ip_loop_monitored(SOCK_STREAM, 0);}Client sockets
Section titled “Client sockets”Client sockets returned by accept() still use std::unique_ptr<socket::Socket> — no change needed for client socket handling.
Supporting Multiple ESPHome Versions
Section titled “Supporting Multiple ESPHome Versions”#if ESPHOME_VERSION_CODE >= VERSION_CODE(2026, 3, 0) std::unique_ptr<socket::ListenSocket> server_;#else std::unique_ptr<socket::Socket> server_;#endifNote: On ESP32 and LibreTiny in 2026.3.0, ListenSocket is a type alias for Socket, so std::unique_ptr<socket::Socket> would still compile. However, using ListenSocket is the correct forward-compatible approach.
Timeline
Section titled “Timeline”- ESPHome 2026.3.0 (March 2026): Socket types devirtualized
- No deprecation period — this is a refactor with minimal external impact
Finding Code That Needs Updates
Section titled “Finding Code That Needs Updates”# Find socket::Socket used for listen socketsgrep -rn 'unique_ptr.*socket::Socket.*server\|socket_ip_loop_monitored' your_component/Questions?
Section titled “Questions?”If you have questions about migrating your external component, please ask in:
- ESPHome Discord - #devs channel
- ESPHome GitHub Discussions
Related Documentation
Section titled “Related Documentation”Copyright © 2026 ESPHome - A project from the Open Home Foundation
Comments
Feel free to leave a comment here to discuss this post with others. You can ask questions, share your experience, or suggest improvements. If you have a question about a specific feature or issue, please consider using the ESPHome Discord. Stick to English and follow ESPHome's code of conduct. These comments exist on a discussion on GitHub, so you can also comment there directly if you prefer.