100.00% Lines (38/38) 100.00% Functions (11/11)
TLA Baseline Branch
Line Hits Code Line Hits Code
1   // 1   //
2   // Copyright (c) 2026 Michael Vandeberg 2   // Copyright (c) 2026 Michael Vandeberg
3   // 3   //
4   // Distributed under the Boost Software License, Version 1.0. (See accompanying 4   // Distributed under the Boost Software License, Version 1.0. (See accompanying
5   // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) 5   // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
6   // 6   //
7   // Official repository: https://github.com/cppalliance/corosio 7   // Official repository: https://github.com/cppalliance/corosio
8   // 8   //
9   9  
10   #ifndef BOOST_COROSIO_RANDOM_ACCESS_FILE_HPP 10   #ifndef BOOST_COROSIO_RANDOM_ACCESS_FILE_HPP
11   #define BOOST_COROSIO_RANDOM_ACCESS_FILE_HPP 11   #define BOOST_COROSIO_RANDOM_ACCESS_FILE_HPP
12   12  
13   #include <boost/corosio/detail/config.hpp> 13   #include <boost/corosio/detail/config.hpp>
14   #include <boost/corosio/detail/platform.hpp> 14   #include <boost/corosio/detail/platform.hpp>
15   #include <boost/corosio/detail/except.hpp> 15   #include <boost/corosio/detail/except.hpp>
16   #include <boost/corosio/detail/native_handle.hpp> 16   #include <boost/corosio/detail/native_handle.hpp>
17   #include <boost/corosio/detail/buffer_param.hpp> 17   #include <boost/corosio/detail/buffer_param.hpp>
18   #include <boost/corosio/detail/op_base.hpp> 18   #include <boost/corosio/detail/op_base.hpp>
19   #include <boost/corosio/file_base.hpp> 19   #include <boost/corosio/file_base.hpp>
20   #include <boost/corosio/io/io_object.hpp> 20   #include <boost/corosio/io/io_object.hpp>
21   #include <boost/capy/io_result.hpp> 21   #include <boost/capy/io_result.hpp>
22   #include <boost/capy/ex/executor_ref.hpp> 22   #include <boost/capy/ex/executor_ref.hpp>
23   #include <boost/capy/ex/execution_context.hpp> 23   #include <boost/capy/ex/execution_context.hpp>
24   #include <boost/capy/ex/io_env.hpp> 24   #include <boost/capy/ex/io_env.hpp>
25   #include <boost/capy/concept/executor.hpp> 25   #include <boost/capy/concept/executor.hpp>
26   #include <boost/capy/buffers.hpp> 26   #include <boost/capy/buffers.hpp>
27   27  
28   #include <concepts> 28   #include <concepts>
29   #include <coroutine> 29   #include <coroutine>
30   #include <cstddef> 30   #include <cstddef>
31   #include <cstdint> 31   #include <cstdint>
32   #include <type_traits> 32   #include <type_traits>
33   #include <filesystem> 33   #include <filesystem>
34   #include <stop_token> 34   #include <stop_token>
35   #include <system_error> 35   #include <system_error>
36   36  
37   namespace boost::corosio { 37   namespace boost::corosio {
38   38  
39   /** An asynchronous random-access file for coroutine I/O. 39   /** An asynchronous random-access file for coroutine I/O.
40   40  
41   Provides asynchronous read and write operations at explicit 41   Provides asynchronous read and write operations at explicit
42   byte offsets, without maintaining an implicit file position. 42   byte offsets, without maintaining an implicit file position.
43   43  
44   On POSIX platforms, file I/O is dispatched to a thread pool 44   On POSIX platforms, file I/O is dispatched to a thread pool
45   (blocking `preadv`/`pwritev`) with completion posted back to 45   (blocking `preadv`/`pwritev`) with completion posted back to
46   the scheduler. On Windows, true overlapped I/O is used via IOCP. 46   the scheduler. On Windows, true overlapped I/O is used via IOCP.
47   47  
48   @par Thread Safety 48   @par Thread Safety
49   Distinct objects: Safe.@n 49   Distinct objects: Safe.@n
50   Shared objects: Unsafe. Multiple concurrent reads and writes 50   Shared objects: Unsafe. Multiple concurrent reads and writes
51   are supported from coroutines sharing the same file object, 51   are supported from coroutines sharing the same file object,
52   but external synchronization is required for non-async 52   but external synchronization is required for non-async
53   operations (open, close, size, resize, etc.). 53   operations (open, close, size, resize, etc.).
54   54  
55   @par Example 55   @par Example
56   @par !example random_access_file 56   @par !example random_access_file
57   */ 57   */
58   class BOOST_COROSIO_DECL random_access_file : public io_object 58   class BOOST_COROSIO_DECL random_access_file : public io_object
59   { 59   {
60   public: 60   public:
61   /** Platform-specific random-access file implementation interface. 61   /** Platform-specific random-access file implementation interface.
62   62  
63   Backends derive from this to provide offset-based file I/O. 63   Backends derive from this to provide offset-based file I/O.
64   */ 64   */
65   struct implementation : io_object::implementation 65   struct implementation : io_object::implementation
66   { 66   {
67   /** Initiate a read at the given offset. 67   /** Initiate a read at the given offset.
68   68  
69   @param offset Byte offset into the file. 69   @param offset Byte offset into the file.
70   @param h Coroutine handle to resume on completion. 70   @param h Coroutine handle to resume on completion.
71   @param ex Executor for dispatching the completion. 71   @param ex Executor for dispatching the completion.
72   @param buf The buffer to read into. 72   @param buf The buffer to read into.
73   @param token Stop token for cancellation. 73   @param token Stop token for cancellation.
74   @param ec Output error code. 74   @param ec Output error code.
75   @param bytes_out Output bytes transferred. 75   @param bytes_out Output bytes transferred.
76   @return Coroutine handle to resume immediately. 76   @return Coroutine handle to resume immediately.
77   */ 77   */
78   virtual std::coroutine_handle<> read_some_at( 78   virtual std::coroutine_handle<> read_some_at(
79   std::uint64_t offset, 79   std::uint64_t offset,
80   std::coroutine_handle<> h, 80   std::coroutine_handle<> h,
81   capy::executor_ref ex, 81   capy::executor_ref ex,
82   buffer_param buf, 82   buffer_param buf,
83   std::stop_token token, 83   std::stop_token token,
84   std::error_code* ec, 84   std::error_code* ec,
85   std::size_t* bytes_out) = 0; 85   std::size_t* bytes_out) = 0;
86   86  
87   /** Initiate a write at the given offset. 87   /** Initiate a write at the given offset.
88   88  
89   @param offset Byte offset into the file. 89   @param offset Byte offset into the file.
90   @param h Coroutine handle to resume on completion. 90   @param h Coroutine handle to resume on completion.
91   @param ex Executor for dispatching the completion. 91   @param ex Executor for dispatching the completion.
92   @param buf The buffer to write from. 92   @param buf The buffer to write from.
93   @param token Stop token for cancellation. 93   @param token Stop token for cancellation.
94   @param ec Output error code. 94   @param ec Output error code.
95   @param bytes_out Output bytes transferred. 95   @param bytes_out Output bytes transferred.
96   @return Coroutine handle to resume immediately. 96   @return Coroutine handle to resume immediately.
97   */ 97   */
98   virtual std::coroutine_handle<> write_some_at( 98   virtual std::coroutine_handle<> write_some_at(
99   std::uint64_t offset, 99   std::uint64_t offset,
100   std::coroutine_handle<> h, 100   std::coroutine_handle<> h,
101   capy::executor_ref ex, 101   capy::executor_ref ex,
102   buffer_param buf, 102   buffer_param buf,
103   std::stop_token token, 103   std::stop_token token,
104   std::error_code* ec, 104   std::error_code* ec,
105   std::size_t* bytes_out) = 0; 105   std::size_t* bytes_out) = 0;
106   106  
107   /// Return the platform file descriptor or handle. 107   /// Return the platform file descriptor or handle.
108   virtual native_handle_type native_handle() const noexcept = 0; 108   virtual native_handle_type native_handle() const noexcept = 0;
109   109  
110   /// Cancel pending asynchronous operations. 110   /// Cancel pending asynchronous operations.
111   virtual void cancel() noexcept = 0; 111   virtual void cancel() noexcept = 0;
112   112  
113   /// Return the file size in bytes. 113   /// Return the file size in bytes.
114   virtual std::uint64_t size() const = 0; 114   virtual std::uint64_t size() const = 0;
115   115  
116   /// Resize the file to @p new_size bytes. 116   /// Resize the file to @p new_size bytes.
117   virtual std::error_code resize(std::uint64_t new_size) noexcept = 0; 117   virtual std::error_code resize(std::uint64_t new_size) noexcept = 0;
118   118  
119   /// Synchronize file data to stable storage. 119   /// Synchronize file data to stable storage.
120   virtual std::error_code sync_data() noexcept = 0; 120   virtual std::error_code sync_data() noexcept = 0;
121   121  
122   /// Synchronize file data and metadata to stable storage. 122   /// Synchronize file data and metadata to stable storage.
123   virtual std::error_code sync_all() noexcept = 0; 123   virtual std::error_code sync_all() noexcept = 0;
124   124  
125   /// Release ownership of the native handle. 125   /// Release ownership of the native handle.
126   virtual native_handle_type release() = 0; 126   virtual native_handle_type release() = 0;
127   127  
128   /// Adopt an existing native handle. 128   /// Adopt an existing native handle.
129   virtual std::error_code assign(native_handle_type handle) noexcept = 0; 129   virtual std::error_code assign(native_handle_type handle) noexcept = 0;
130   }; 130   };
131   131  
132   /** Awaitable for async read-at operations. */ 132   /** Awaitable for async read-at operations. */
133   template<class MutableBufferSequence> 133   template<class MutableBufferSequence>
134   struct read_some_at_awaitable 134   struct read_some_at_awaitable
135   : detail::bytes_op_base<read_some_at_awaitable<MutableBufferSequence>> 135   : detail::bytes_op_base<read_some_at_awaitable<MutableBufferSequence>>
136   { 136   {
137   random_access_file& f_; 137   random_access_file& f_;
138   std::uint64_t offset_; 138   std::uint64_t offset_;
139   MutableBufferSequence buffers_; 139   MutableBufferSequence buffers_;
140   140  
HITCBC 141   343 read_some_at_awaitable( 141   343 read_some_at_awaitable(
142   random_access_file& f, 142   random_access_file& f,
143   std::uint64_t offset, 143   std::uint64_t offset,
144   MutableBufferSequence 144   MutableBufferSequence
145   buffers) noexcept(std:: 145   buffers) noexcept(std::
146   is_nothrow_move_constructible_v< 146   is_nothrow_move_constructible_v<
147   MutableBufferSequence>) 147   MutableBufferSequence>)
HITCBC 148   343 : f_(f) 148   343 : f_(f)
HITCBC 149   343 , offset_(offset) 149   343 , offset_(offset)
HITCBC 150   343 , buffers_(std::move(buffers)) 150   343 , buffers_(std::move(buffers))
151   { 151   {
HITCBC 152   343 } 152   343 }
153   153  
154   std::coroutine_handle<> 154   std::coroutine_handle<>
HITCBC 155   337 dispatch(std::coroutine_handle<> h, capy::executor_ref ex) const 155   337 dispatch(std::coroutine_handle<> h, capy::executor_ref ex) const
156   { 156   {
HITCBC 157   674 return f_.get().read_some_at( 157   674 return f_.get().read_some_at(
HITCBC 158   337 offset_, h, ex, buffers_, this->token_, &this->ec_, 158   337 offset_, h, ex, buffers_, this->token_, &this->ec_,
HITCBC 159   674 &this->bytes_); 159   674 &this->bytes_);
160   } 160   }
161   }; 161   };
162   162  
163   /** Awaitable for async write-at operations. */ 163   /** Awaitable for async write-at operations. */
164   template<class ConstBufferSequence> 164   template<class ConstBufferSequence>
165   struct write_some_at_awaitable 165   struct write_some_at_awaitable
166   : detail::bytes_op_base<write_some_at_awaitable<ConstBufferSequence>> 166   : detail::bytes_op_base<write_some_at_awaitable<ConstBufferSequence>>
167   { 167   {
168   random_access_file& f_; 168   random_access_file& f_;
169   std::uint64_t offset_; 169   std::uint64_t offset_;
170   ConstBufferSequence buffers_; 170   ConstBufferSequence buffers_;
171   171  
HITCBC 172   93 write_some_at_awaitable( 172   93 write_some_at_awaitable(
173   random_access_file& f, 173   random_access_file& f,
174   std::uint64_t offset, 174   std::uint64_t offset,
175   ConstBufferSequence 175   ConstBufferSequence
176   buffers) noexcept(std:: 176   buffers) noexcept(std::
177   is_nothrow_move_constructible_v< 177   is_nothrow_move_constructible_v<
178   ConstBufferSequence>) 178   ConstBufferSequence>)
HITCBC 179   93 : f_(f) 179   93 : f_(f)
HITCBC 180   93 , offset_(offset) 180   93 , offset_(offset)
HITCBC 181   93 , buffers_(std::move(buffers)) 181   93 , buffers_(std::move(buffers))
182   { 182   {
HITCBC 183   93 } 183   93 }
184   184  
185   std::coroutine_handle<> 185   std::coroutine_handle<>
HITCBC 186   89 dispatch(std::coroutine_handle<> h, capy::executor_ref ex) const 186   89 dispatch(std::coroutine_handle<> h, capy::executor_ref ex) const
187   { 187   {
HITCBC 188   178 return f_.get().write_some_at( 188   178 return f_.get().write_some_at(
HITCBC 189   89 offset_, h, ex, buffers_, this->token_, &this->ec_, 189   89 offset_, h, ex, buffers_, this->token_, &this->ec_,
HITCBC 190   178 &this->bytes_); 190   178 &this->bytes_);
191   } 191   }
192   }; 192   };
193   193  
194   public: 194   public:
195   /** Destructor. 195   /** Destructor.
196   196  
197   Closes the file if open, cancelling any pending operations. 197   Closes the file if open, cancelling any pending operations.
198   */ 198   */
199   ~random_access_file() override; 199   ~random_access_file() override;
200   200  
201   /** Construct from an execution context. 201   /** Construct from an execution context.
202   202  
203   @param ctx The execution context that will own this file. 203   @param ctx The execution context that will own this file.
204   */ 204   */
205   explicit random_access_file(capy::execution_context& ctx); 205   explicit random_access_file(capy::execution_context& ctx);
206   206  
207   /** Construct from an executor. 207   /** Construct from an executor.
208   208  
209   @param ex The executor whose context will own this file. 209   @param ex The executor whose context will own this file.
210   */ 210   */
211   template<class Ex> 211   template<class Ex>
212   requires(!std::same_as<std::remove_cvref_t<Ex>, random_access_file>) && 212   requires(!std::same_as<std::remove_cvref_t<Ex>, random_access_file>) &&
213   capy::Executor<Ex> 213   capy::Executor<Ex>
HITCBC 214   2 explicit random_access_file(Ex const& ex) : random_access_file(ex.context()) 214   2 explicit random_access_file(Ex const& ex) : random_access_file(ex.context())
215   { 215   {
HITCBC 216   2 } 216   2 }
217   217  
218   /** Move constructor. */ 218   /** Move constructor. */
HITCBC 219   2 random_access_file(random_access_file&& other) noexcept 219   2 random_access_file(random_access_file&& other) noexcept
HITCBC 220   2 : io_object(std::move(other)) 220   2 : io_object(std::move(other))
221   { 221   {
HITCBC 222   2 } 222   2 }
223   223  
224   /** Move assignment operator. */ 224   /** Move assignment operator. */
225   random_access_file& operator=(random_access_file&& other) noexcept 225   random_access_file& operator=(random_access_file&& other) noexcept
226   { 226   {
227   if (this != &other) 227   if (this != &other)
228   { 228   {
229   close(); 229   close();
230   h_ = std::move(other.h_); 230   h_ = std::move(other.h_);
231   } 231   }
232   return *this; 232   return *this;
233   } 233   }
234   234  
235   random_access_file(random_access_file const&) = delete; 235   random_access_file(random_access_file const&) = delete;
236   random_access_file& operator=(random_access_file const&) = delete; 236   random_access_file& operator=(random_access_file const&) = delete;
237   237  
238   /** Open a file. 238   /** Open a file.
239   239  
240   Failures such as a missing file or insufficient permissions 240   Failures such as a missing file or insufficient permissions
241   are expected runtime conditions and are reported through the 241   are expected runtime conditions and are reported through the
242   returned error code. If the file is already open, it is 242   returned error code. If the file is already open, it is
243   closed first. 243   closed first.
244   244  
245   @param path The filesystem path to open. 245   @param path The filesystem path to open.
246   @param mode Bitmask of @ref file_base::flags specifying 246   @param mode Bitmask of @ref file_base::flags specifying
247   access mode and creation behavior. 247   access mode and creation behavior.
248   248  
249   @return The error code, empty on success. 249   @return The error code, empty on success.
250   */ 250   */
251   [[nodiscard]] std::error_code open( 251   [[nodiscard]] std::error_code open(
252   std::filesystem::path const& path, 252   std::filesystem::path const& path,
253   file_base::flags mode = file_base::read_only) noexcept; 253   file_base::flags mode = file_base::read_only) noexcept;
254   254  
255   /** Close the file. 255   /** Close the file.
256   256  
257   Releases file resources. Any pending operations complete 257   Releases file resources. Any pending operations complete
258   with `errc::operation_canceled`. 258   with `errc::operation_canceled`.
259   */ 259   */
260   void close() noexcept; 260   void close() noexcept;
261   261  
262   /** Check if the file is open. */ 262   /** Check if the file is open. */
HITCBC 263   1082 bool is_open() const noexcept 263   1082 bool is_open() const noexcept
264   { 264   {
265   #if BOOST_COROSIO_HAS_IOCP && !defined(BOOST_COROSIO_MRDOCS) 265   #if BOOST_COROSIO_HAS_IOCP && !defined(BOOST_COROSIO_MRDOCS)
266   return h_ && get().native_handle() != ~native_handle_type(0); 266   return h_ && get().native_handle() != ~native_handle_type(0);
267   #else 267   #else
HITCBC 268   1082 return h_ && get().native_handle() >= 0; 268   1082 return h_ && get().native_handle() >= 0;
269   #endif 269   #endif
270   } 270   }
271   271  
272   /** Read data at the given offset. 272   /** Read data at the given offset.
273   273  
274   @param offset Byte offset into the file. 274   @param offset Byte offset into the file.
275   @param buffers The buffer sequence to read into. 275   @param buffers The buffer sequence to read into.
276   276  
277   @return An awaitable yielding `(error_code, std::size_t)`. 277   @return An awaitable yielding `(error_code, std::size_t)`.
278   278  
279   A closed file reports `errc::bad_file_descriptor`. 279   A closed file reports `errc::bad_file_descriptor`.
280   */ 280   */
281   template<capy::MutableBufferSequence MB> 281   template<capy::MutableBufferSequence MB>
HITCBC 282   343 [[nodiscard]] auto read_some_at(std::uint64_t offset, MB const& buffers) 282   343 [[nodiscard]] auto read_some_at(std::uint64_t offset, MB const& buffers)
283   { 283   {
HITCBC 284   343 read_some_at_awaitable<MB> aw(*this, offset, buffers); 284   343 read_some_at_awaitable<MB> aw(*this, offset, buffers);
HITCBC 285   343 if (!is_open()) 285   343 if (!is_open())
HITCBC 286   2 aw.ec_ = make_error_code(std::errc::bad_file_descriptor); 286   2 aw.ec_ = make_error_code(std::errc::bad_file_descriptor);
HITCBC 287   343 return aw; 287   343 return aw;
288   } 288   }
289   289  
290   /** Write data at the given offset. 290   /** Write data at the given offset.
291   291  
292   @param offset Byte offset into the file. 292   @param offset Byte offset into the file.
293   @param buffers The buffer sequence to write from. 293   @param buffers The buffer sequence to write from.
294   294  
295   @return An awaitable yielding `(error_code, std::size_t)`. 295   @return An awaitable yielding `(error_code, std::size_t)`.
296   296  
297   A closed file reports `errc::bad_file_descriptor`. 297   A closed file reports `errc::bad_file_descriptor`.
298   */ 298   */
299   template<capy::ConstBufferSequence CB> 299   template<capy::ConstBufferSequence CB>
HITCBC 300   93 [[nodiscard]] auto write_some_at(std::uint64_t offset, CB const& buffers) 300   93 [[nodiscard]] auto write_some_at(std::uint64_t offset, CB const& buffers)
301   { 301   {
HITCBC 302   93 write_some_at_awaitable<CB> aw(*this, offset, buffers); 302   93 write_some_at_awaitable<CB> aw(*this, offset, buffers);
HITCBC 303   93 if (!is_open()) 303   93 if (!is_open())
HITCBC 304   2 aw.ec_ = make_error_code(std::errc::bad_file_descriptor); 304   2 aw.ec_ = make_error_code(std::errc::bad_file_descriptor);
HITCBC 305   93 return aw; 305   93 return aw;
306   } 306   }
307   307  
308   /** Cancel pending asynchronous operations. */ 308   /** Cancel pending asynchronous operations. */
309   void cancel() noexcept; 309   void cancel() noexcept;
310   310  
311   /** Get the native file descriptor or handle. */ 311   /** Get the native file descriptor or handle. */
312   native_handle_type native_handle() const noexcept; 312   native_handle_type native_handle() const noexcept;
313   313  
314   /** Return the file size in bytes. 314   /** Return the file size in bytes.
315   315  
316   @throws std::system_error If the file is not open, or if the 316   @throws std::system_error If the file is not open, or if the
317   underlying size query fails. 317   underlying size query fails.
318   */ 318   */
319   std::uint64_t size() const; 319   std::uint64_t size() const;
320   320  
321   /** Resize the file to @p new_size bytes. 321   /** Resize the file to @p new_size bytes.
322   322  
323   Failures such as insufficient disk space are reported 323   Failures such as insufficient disk space are reported
324   through the returned error code. A closed file reports 324   through the returned error code. A closed file reports
325   `errc::bad_file_descriptor`. 325   `errc::bad_file_descriptor`.
326   326  
327   @param new_size The new file size. 327   @param new_size The new file size.
328   328  
329   @return The error code, empty on success. 329   @return The error code, empty on success.
330   */ 330   */
331   [[nodiscard]] std::error_code resize(std::uint64_t new_size) noexcept; 331   [[nodiscard]] std::error_code resize(std::uint64_t new_size) noexcept;
332   332  
333   /** Synchronize file data to stable storage. 333   /** Synchronize file data to stable storage.
334   334  
335   Write-back failures such as device I/O errors surface here 335   Write-back failures such as device I/O errors surface here
336   and are reported through the returned error code. A closed 336   and are reported through the returned error code. A closed
337   file reports `errc::bad_file_descriptor`. 337   file reports `errc::bad_file_descriptor`.
338   338  
339   @return The error code, empty on success. 339   @return The error code, empty on success.
340   */ 340   */
341   [[nodiscard]] std::error_code sync_data() noexcept; 341   [[nodiscard]] std::error_code sync_data() noexcept;
342   342  
343   /** Synchronize file data and metadata to stable storage. 343   /** Synchronize file data and metadata to stable storage.
344   344  
345   Write-back failures such as device I/O errors surface here 345   Write-back failures such as device I/O errors surface here
346   and are reported through the returned error code. A closed 346   and are reported through the returned error code. A closed
347   file reports `errc::bad_file_descriptor`. 347   file reports `errc::bad_file_descriptor`.
348   348  
349   @return The error code, empty on success. 349   @return The error code, empty on success.
350   */ 350   */
351   [[nodiscard]] std::error_code sync_all() noexcept; 351   [[nodiscard]] std::error_code sync_all() noexcept;
352   352  
353   /** Release ownership of the native handle. 353   /** Release ownership of the native handle.
354   354  
355   The file object becomes not-open. The caller is 355   The file object becomes not-open. The caller is
356   responsible for closing the returned handle. 356   responsible for closing the returned handle.
357   357  
358   @return The native file descriptor or handle. 358   @return The native file descriptor or handle.
359   359  
360   @throws std::system_error `errc::bad_file_descriptor` if the 360   @throws std::system_error `errc::bad_file_descriptor` if the
361   file is not open. 361   file is not open.
362   */ 362   */
363   native_handle_type release(); 363   native_handle_type release();
364   364  
365   /** Adopt an existing native handle. 365   /** Adopt an existing native handle.
366   366  
367   Closes any currently open file before adopting. 367   Closes any currently open file before adopting.
368   The file object takes ownership of the handle. Handles 368   The file object takes ownership of the handle. Handles
369   created elsewhere may be unsuitable for asynchronous I/O; 369   created elsewhere may be unsuitable for asynchronous I/O;
370   such failures are reported through the returned error code. 370   such failures are reported through the returned error code.
371   371  
372   @param handle The native file descriptor or handle. 372   @param handle The native file descriptor or handle.
373   373  
374   @return The error code, empty on success. 374   @return The error code, empty on success.
375   */ 375   */
376   [[nodiscard]] std::error_code assign(native_handle_type handle) noexcept; 376   [[nodiscard]] std::error_code assign(native_handle_type handle) noexcept;
377   377  
378   protected: 378   protected:
379   /// Construct from a pre-built handle (for native_random_access_file). 379   /// Construct from a pre-built handle (for native_random_access_file).
HITCBC 380   16 explicit random_access_file(handle h) noexcept : io_object(std::move(h)) {} 380   16 explicit random_access_file(handle h) noexcept : io_object(std::move(h)) {}
381   381  
382   private: 382   private:
HITCBC 383   1765 inline implementation& get() const noexcept 383   1765 inline implementation& get() const noexcept
384   { 384   {
HITCBC 385   1765 return *static_cast<implementation*>(h_.get()); 385   1765 return *static_cast<implementation*>(h_.get());
386   } 386   }
387   }; 387   };
388   388  
389   } // namespace boost::corosio 389   } // namespace boost::corosio
390   390  
391   #endif // BOOST_COROSIO_RANDOM_ACCESS_FILE_HPP 391   #endif // BOOST_COROSIO_RANDOM_ACCESS_FILE_HPP