Allow taking from pointer lists

This introduces `tc_task_list_take`, supporting taking ownership of an
item in a task list.

TCTaskList is the only pointer list, but this is a generic and could be
used for other types.
This commit is contained in:
Dustin J. Mitchell
2022-03-13 00:08:55 +00:00
parent 9355e1a728
commit 33a3b980d0
10 changed files with 278 additions and 52 deletions

View File

@@ -561,6 +561,53 @@ static void test_task_taskmap(void) {
tc_replica_free(rep); tc_replica_free(rep);
} }
// taking from a task list behaves correctly
static void test_task_list_take(void) {
TCReplica *rep = tc_replica_new_in_memory();
TEST_ASSERT_NULL(tc_replica_error(rep).ptr);
TCTask *task1 = tc_replica_new_task(rep, TC_STATUS_PENDING, tc_string_borrow("t"));
TEST_ASSERT_NOT_NULL(task1);
TCTask *task2 = tc_replica_new_task(rep, TC_STATUS_PENDING, tc_string_borrow("t"));
TEST_ASSERT_NOT_NULL(task2);
tc_task_free(task2);
TCString desc;
TCTaskList tasks = tc_replica_all_tasks(rep);
TEST_ASSERT_NOT_NULL(tasks.items);
TEST_ASSERT_EQUAL(2, tasks.len);
task1 = tc_task_list_take(&tasks, 5); // out of bounds
TEST_ASSERT_NULL(task1);
task1 = tc_task_list_take(&tasks, 0);
TEST_ASSERT_NOT_NULL(task1);
desc = tc_task_get_description(task1);
TEST_ASSERT_EQUAL_STRING("t", tc_string_content(&desc));
tc_string_free(&desc);
task2 = tc_task_list_take(&tasks, 1);
TEST_ASSERT_NOT_NULL(task2);
desc = tc_task_get_description(task2);
TEST_ASSERT_EQUAL_STRING("t", tc_string_content(&desc));
tc_string_free(&desc);
tc_task_free(task1);
tc_task_free(task2);
task1 = tc_task_list_take(&tasks, 0); // already taken
TEST_ASSERT_NULL(task1);
task1 = tc_task_list_take(&tasks, 5); // out of bounds
TEST_ASSERT_NULL(task1);
tc_task_list_free(&tasks);
TEST_ASSERT_NULL(tasks.items);
tc_replica_free(rep);
}
int task_tests(void) { int task_tests(void) {
UNITY_BEGIN(); UNITY_BEGIN();
// each test case above should be named here, in order. // each test case above should be named here, in order.
@@ -578,5 +625,6 @@ int task_tests(void) {
RUN_TEST(test_task_annotations); RUN_TEST(test_task_annotations);
RUN_TEST(test_task_udas); RUN_TEST(test_task_udas);
RUN_TEST(test_task_taskmap); RUN_TEST(test_task_taskmap);
RUN_TEST(test_task_list_take);
return UNITY_END(); return UNITY_END();
} }

View File

@@ -75,13 +75,13 @@ pub struct TCAnnotationList {
/// array of annotations. these remain owned by the TCAnnotationList instance and will be freed by /// array of annotations. these remain owned by the TCAnnotationList instance and will be freed by
/// tc_annotation_list_free. This pointer is never NULL for a valid TCAnnotationList. /// tc_annotation_list_free. This pointer is never NULL for a valid TCAnnotationList.
items: *const TCAnnotation, items: *mut TCAnnotation,
} }
impl CList for TCAnnotationList { impl CList for TCAnnotationList {
type Element = TCAnnotation; type Element = TCAnnotation;
unsafe fn from_raw_parts(items: *const Self::Element, len: usize, cap: usize) -> Self { unsafe fn from_raw_parts(items: *mut Self::Element, len: usize, cap: usize) -> Self {
TCAnnotationList { TCAnnotationList {
len, len,
_capacity: cap, _capacity: cap,
@@ -89,7 +89,16 @@ impl CList for TCAnnotationList {
} }
} }
fn into_raw_parts(self) -> (*const Self::Element, usize, usize) { fn slice(&mut self) -> &mut [Self::Element] {
// SAFETY:
// - because we have &mut self, we have read/write access to items[0..len]
// - all items are properly initialized Element's
// - return value lifetime is equal to &mmut self's, so access is exclusive
// - items and len came from Vec, so total size is < isize::MAX
unsafe { std::slice::from_raw_parts_mut(self.items, self.len) }
}
fn into_raw_parts(self) -> (*mut Self::Element, usize, usize) {
(self.items, self.len, self._capacity) (self.items, self.len, self._capacity)
} }
} }

View File

@@ -50,13 +50,13 @@ pub struct TCKVList {
/// array of TCKV's. these remain owned by the TCKVList instance and will be freed by /// array of TCKV's. these remain owned by the TCKVList instance and will be freed by
/// tc_kv_list_free. This pointer is never NULL for a valid TCKVList. /// tc_kv_list_free. This pointer is never NULL for a valid TCKVList.
items: *const TCKV, items: *mut TCKV,
} }
impl CList for TCKVList { impl CList for TCKVList {
type Element = TCKV; type Element = TCKV;
unsafe fn from_raw_parts(items: *const Self::Element, len: usize, cap: usize) -> Self { unsafe fn from_raw_parts(items: *mut Self::Element, len: usize, cap: usize) -> Self {
TCKVList { TCKVList {
len, len,
_capacity: cap, _capacity: cap,
@@ -64,7 +64,16 @@ impl CList for TCKVList {
} }
} }
fn into_raw_parts(self) -> (*const Self::Element, usize, usize) { fn slice(&mut self) -> &mut [Self::Element] {
// SAFETY:
// - because we have &mut self, we have read/write access to items[0..len]
// - all items are properly initialized Element's
// - return value lifetime is equal to &mmut self's, so access is exclusive
// - items and len came from Vec, so total size is < isize::MAX
unsafe { std::slice::from_raw_parts_mut(self.items, self.len) }
}
fn into_raw_parts(self) -> (*mut Self::Element, usize, usize) {
(self.items, self.len, self._capacity) (self.items, self.len, self._capacity)
} }
} }

View File

@@ -177,12 +177,14 @@ pub unsafe extern "C" fn tc_replica_all_tasks(rep: *mut TCReplica) -> TCTaskList
.all_tasks()? .all_tasks()?
.drain() .drain()
.map(|(_uuid, t)| { .map(|(_uuid, t)| {
NonNull::new( Some(
// SAFETY: NonNull::new(
// - caller promises to free this value (via freeing the list) // SAFETY:
unsafe { TCTask::from(t).return_ptr() }, // - caller promises to free this value (via freeing the list)
unsafe { TCTask::from(t).return_ptr() },
)
.expect("TCTask::return_ptr returned NULL"),
) )
.expect("TCTask::return_ptr returned NULL")
}) })
.collect(); .collect();
// SAFETY: // SAFETY:

View File

@@ -374,13 +374,13 @@ pub struct TCStringList {
/// TCStringList representing each string. these remain owned by the TCStringList instance and will /// TCStringList representing each string. these remain owned by the TCStringList instance and will
/// be freed by tc_string_list_free. This pointer is never NULL for a valid TCStringList, and the /// be freed by tc_string_list_free. This pointer is never NULL for a valid TCStringList, and the
/// *TCStringList at indexes 0..len-1 are not NULL. /// *TCStringList at indexes 0..len-1 are not NULL.
items: *const TCString, items: *mut TCString,
} }
impl CList for TCStringList { impl CList for TCStringList {
type Element = TCString; type Element = TCString;
unsafe fn from_raw_parts(items: *const Self::Element, len: usize, cap: usize) -> Self { unsafe fn from_raw_parts(items: *mut Self::Element, len: usize, cap: usize) -> Self {
TCStringList { TCStringList {
len, len,
_capacity: cap, _capacity: cap,
@@ -388,7 +388,16 @@ impl CList for TCStringList {
} }
} }
fn into_raw_parts(self) -> (*const Self::Element, usize, usize) { fn slice(&mut self) -> &mut [Self::Element] {
// SAFETY:
// - because we have &mut self, we have read/write access to items[0..len]
// - all items are properly initialized Element's
// - return value lifetime is equal to &mmut self's, so access is exclusive
// - items and len came from Vec, so total size is < isize::MAX
unsafe { std::slice::from_raw_parts_mut(self.items, self.len) }
}
fn into_raw_parts(self) -> (*mut Self::Element, usize, usize) {
(self.items, self.len, self._capacity) (self.items, self.len, self._capacity)
} }
} }

View File

@@ -171,7 +171,10 @@ impl TryFrom<RustString<'static>> for Tag {
/// TCTaskList represents a list of tasks. /// TCTaskList represents a list of tasks.
/// ///
/// The content of this struct must be treated as read-only. /// The content of this struct must be treated as read-only: no fields or anything they reference
/// should be modified directly by C code.
///
/// When an item is taken from this list, its pointer in `items` is set to NULL.
#[repr(C)] #[repr(C)]
pub struct TCTaskList { pub struct TCTaskList {
/// number of tasks in items /// number of tasks in items
@@ -183,13 +186,13 @@ pub struct TCTaskList {
/// array of pointers representing each task. these remain owned by the TCTaskList instance and /// array of pointers representing each task. these remain owned by the TCTaskList instance and
/// will be freed by tc_task_list_free. This pointer is never NULL for a valid TCTaskList, /// will be freed by tc_task_list_free. This pointer is never NULL for a valid TCTaskList,
/// and the *TCTaskList at indexes 0..len-1 are not NULL. /// and the *TCTaskList at indexes 0..len-1 are not NULL.
items: *const NonNull<TCTask>, items: *mut Option<NonNull<TCTask>>,
} }
impl CList for TCTaskList { impl CList for TCTaskList {
type Element = NonNull<TCTask>; type Element = Option<NonNull<TCTask>>;
unsafe fn from_raw_parts(items: *const Self::Element, len: usize, cap: usize) -> Self { unsafe fn from_raw_parts(items: *mut Self::Element, len: usize, cap: usize) -> Self {
TCTaskList { TCTaskList {
len, len,
_capacity: cap, _capacity: cap,
@@ -197,7 +200,16 @@ impl CList for TCTaskList {
} }
} }
fn into_raw_parts(self) -> (*const Self::Element, usize, usize) { fn slice(&mut self) -> &mut [Self::Element] {
// SAFETY:
// - because we have &mut self, we have read/write access to items[0..len]
// - all items are properly initialized Element's
// - return value lifetime is equal to &mmut self's, so access is exclusive
// - items and len came from Vec, so total size is < isize::MAX
unsafe { std::slice::from_raw_parts_mut(self.items, self.len) }
}
fn into_raw_parts(self) -> (*mut Self::Element, usize, usize) {
(self.items, self.len, self._capacity) (self.items, self.len, self._capacity)
} }
} }
@@ -813,17 +825,39 @@ pub unsafe extern "C" fn tc_task_free(task: *mut TCTask) {
drop(tctask); drop(tctask);
} }
/// Take an item from a TCTaskList. After this call, the indexed item is no longer associated
/// with the list and becomes the caller's responsibility, just as if it had been returned from
/// `tc_replica_get_task`.
///
/// The corresponding element in the `items` array will be set to NULL. If that field is already
/// NULL (that is, if the item has already been taken), this function will return NULL. If the
/// index is out of bounds, this function will also return NULL.
///
/// The passed TCTaskList remains owned by the caller.
#[no_mangle]
pub unsafe extern "C" fn tc_task_list_take(tasks: *mut TCTaskList, index: usize) -> *mut TCTask {
// SAFETY:
// - tasks is not NULL and points to a valid TCTaskList (caller is not allowed to
// modify the list directly, and tc_task_list_take leaves the list valid)
let p = unsafe { take_optional_pointer_list_item(tasks, index) };
if let Some(p) = p {
p.as_ptr()
} else {
std::ptr::null_mut()
}
}
/// Free a TCTaskList instance. The instance, and all TCTaskList it contains, must not be used after /// Free a TCTaskList instance. The instance, and all TCTaskList it contains, must not be used after
/// this call. /// this call.
/// ///
/// When this call returns, the `items` pointer will be NULL, signalling an invalid TCTaskList. /// When this call returns, the `items` pointer will be NULL, signalling an invalid TCTaskList.
#[no_mangle] #[no_mangle]
pub unsafe extern "C" fn tc_task_list_free(tctasks: *mut TCTaskList) { pub unsafe extern "C" fn tc_task_list_free(tasks: *mut TCTaskList) {
// SAFETY: // SAFETY:
// - tctasks is not NULL and points to a valid TCTaskList (caller is not allowed to // - tasks is not NULL and points to a valid TCTaskList (caller is not allowed to
// modify the list) // modify the list directly, and tc_task_list_take leaves the list valid)
// - caller promises not to use the value after return // - caller promises not to use the value after return
unsafe { drop_pointer_list(tctasks) }; unsafe { drop_optional_pointer_list(tasks) };
} }
#[cfg(test)] #[cfg(test)]
@@ -832,19 +866,19 @@ mod test {
#[test] #[test]
fn empty_list_has_non_null_pointer() { fn empty_list_has_non_null_pointer() {
let tctasks = unsafe { TCTaskList::return_val(Vec::new()) }; let tasks = unsafe { TCTaskList::return_val(Vec::new()) };
assert!(!tctasks.items.is_null()); assert!(!tasks.items.is_null());
assert_eq!(tctasks.len, 0); assert_eq!(tasks.len, 0);
assert_eq!(tctasks._capacity, 0); assert_eq!(tasks._capacity, 0);
} }
#[test] #[test]
fn free_sets_null_pointer() { fn free_sets_null_pointer() {
let mut tctasks = unsafe { TCTaskList::return_val(Vec::new()) }; let mut tasks = unsafe { TCTaskList::return_val(Vec::new()) };
// SAFETY: testing expected behavior // SAFETY: testing expected behavior
unsafe { tc_task_list_free(&mut tctasks) }; unsafe { tc_task_list_free(&mut tasks) };
assert!(tctasks.items.is_null()); assert!(tasks.items.is_null());
assert_eq!(tctasks.len, 0); assert_eq!(tasks.len, 0);
assert_eq!(tctasks._capacity, 0); assert_eq!(tasks._capacity, 0);
} }
} }

View File

@@ -150,13 +150,17 @@ pub(crate) trait PassByPointer: Sized {
/// The PassByValue trait will be implemented automatically, converting between the C type and /// The PassByValue trait will be implemented automatically, converting between the C type and
/// `Vec<Element>`. /// `Vec<Element>`.
/// ///
/// For most cases, it is only necessary to implement `tc_.._free` that calls either /// The element type can be PassByValue or PassByPointer. If the latter, it should use either
/// drop_value_list (if Element is PassByValue) or drop_pointer_list (if element is PassByPointer). /// `NonNull<T>` or `Option<NonNull<T>>` to represent the element. The latter is an "optional
/// pointer list", where elements can be omitted.
///
/// For most cases, it is only necessary to implement `tc_.._free` that calls one of the
/// drop_..._list functions.
/// ///
/// # Safety /// # Safety
/// ///
/// The C type must be documented as read-only. None of the fields may be modified, nor anything /// The C type must be documented as read-only. None of the fields may be modified, nor anything
/// accessible via the `items` array. /// accessible via the `items` array. The exception is modification via "taking" elements.
/// ///
/// This class guarantees that the items pointer is non-NULL for any valid list (even when len=0). /// This class guarantees that the items pointer is non-NULL for any valid list (even when len=0).
pub(crate) trait CList: Sized { pub(crate) trait CList: Sized {
@@ -169,18 +173,21 @@ pub(crate) trait CList: Sized {
/// The arguments must either: /// The arguments must either:
/// - be NULL, 0, and 0, respectively; or /// - be NULL, 0, and 0, respectively; or
/// - be valid for Vec::from_raw_parts /// - be valid for Vec::from_raw_parts
unsafe fn from_raw_parts(items: *const Self::Element, len: usize, cap: usize) -> Self; unsafe fn from_raw_parts(items: *mut Self::Element, len: usize, cap: usize) -> Self;
/// Return a mutable slice representing the elements in this list.
fn slice(&mut self) -> &mut [Self::Element];
/// Get the items, len, and capacity (in that order) for this instance. These must be /// Get the items, len, and capacity (in that order) for this instance. These must be
/// precisely the same values passed tearlier to `from_raw_parts`. /// precisely the same values passed tearlier to `from_raw_parts`.
fn into_raw_parts(self) -> (*const Self::Element, usize, usize); fn into_raw_parts(self) -> (*mut Self::Element, usize, usize);
/// Generate a NULL value. By default this is a NULL items pointer with zero length and /// Generate a NULL value. By default this is a NULL items pointer with zero length and
/// capacity. /// capacity.
fn null_value() -> Self { fn null_value() -> Self {
// SAFETY: // SAFETY:
// - satisfies the first case in from_raw_parts' safety documentation // - satisfies the first case in from_raw_parts' safety documentation
unsafe { Self::from_raw_parts(std::ptr::null(), 0, 0) } unsafe { Self::from_raw_parts(std::ptr::null_mut(), 0, 0) }
} }
} }
@@ -225,6 +232,7 @@ where
/// - List must be non-NULL and point to a valid CL instance /// - List must be non-NULL and point to a valid CL instance
/// - The caller must not use the value array points to after this function, as /// - The caller must not use the value array points to after this function, as
/// it has been freed. It will be replaced with the null value. /// it has been freed. It will be replaced with the null value.
#[allow(dead_code)] // this was useful once, and might be again?
pub(crate) unsafe fn drop_pointer_list<CL, T>(list: *mut CL) pub(crate) unsafe fn drop_pointer_list<CL, T>(list: *mut CL)
where where
CL: CList<Element = NonNull<T>>, CL: CList<Element = NonNull<T>>,
@@ -246,6 +254,79 @@ where
drop(vec); drop(vec);
} }
/// Given a CList containing optional pointers, drop all of the non-null pointed-to values and the
/// list.
///
/// This is a convenience function for `tc_.._list_free` functions, for lists from which items
/// can be taken.
///
/// # Safety
///
/// - List must be non-NULL and point to a valid CL instance
/// - The caller must not use the value array points to after this function, as
/// it has been freed. It will be replaced with the null value.
pub(crate) unsafe fn drop_optional_pointer_list<CL, T>(list: *mut CL)
where
CL: CList<Element = Option<NonNull<T>>>,
T: PassByPointer,
{
debug_assert!(!list.is_null());
// SAFETY:
// - *list is a valid CL (promised by caller)
let mut vec = unsafe { CL::take_val_from_arg(list, CL::null_value()) };
// first, drop each of the elements in turn
for e in vec.drain(..) {
if let Some(e) = e {
// SAFETY:
// - e is a valid Element (promised by caller)
// - e is owned
drop(unsafe { PassByPointer::take_from_ptr_arg(e.as_ptr()) });
}
}
// then drop the vector
drop(vec);
}
/// Take a value from an optional pointer list, returning the value and replacing its array
/// element with NULL.
///
/// This is a convenience function for `tc_.._list_take` functions, for lists from which items
/// can be taken.
///
/// The returned value will be None if the element has already been taken, or if the index is
/// out of bounds.
///
/// # Safety
///
/// - List must be non-NULL and point to a valid CL instance
pub(crate) unsafe fn take_optional_pointer_list_item<CL, T>(
list: *mut CL,
index: usize,
) -> Option<NonNull<T>>
where
CL: CList<Element = Option<NonNull<T>>>,
T: PassByPointer,
{
debug_assert!(!list.is_null());
// SAFETy:
// - list is properly aligned, dereferencable, and points to an initialized CL, since it is valid
// - the lifetime of the resulting reference is limited to this function, during which time
// nothing else refers to this memory.
let slice = list.as_mut().unwrap().slice();
if let Some(elt_ref) = slice.get_mut(index) {
let mut rv = None;
if let Some(elt) = elt_ref.as_mut() {
rv = Some(*elt);
*elt_ref = None; // clear out the array element
}
rv
} else {
None // index out of bounds
}
}
impl<A> PassByValue for A impl<A> PassByValue for A
where where
A: CList, A: CList,

View File

@@ -72,13 +72,13 @@ pub struct TCUdaList {
/// array of UDAs. These remain owned by the TCUdaList instance and will be freed by /// array of UDAs. These remain owned by the TCUdaList instance and will be freed by
/// tc_uda_list_free. This pointer is never NULL for a valid TCUdaList. /// tc_uda_list_free. This pointer is never NULL for a valid TCUdaList.
items: *const TCUda, items: *mut TCUda,
} }
impl CList for TCUdaList { impl CList for TCUdaList {
type Element = TCUda; type Element = TCUda;
unsafe fn from_raw_parts(items: *const Self::Element, len: usize, cap: usize) -> Self { unsafe fn from_raw_parts(items: *mut Self::Element, len: usize, cap: usize) -> Self {
TCUdaList { TCUdaList {
len, len,
_capacity: cap, _capacity: cap,
@@ -86,7 +86,16 @@ impl CList for TCUdaList {
} }
} }
fn into_raw_parts(self) -> (*const Self::Element, usize, usize) { fn slice(&mut self) -> &mut [Self::Element] {
// SAFETY:
// - because we have &mut self, we have read/write access to items[0..len]
// - all items are properly initialized Element's
// - return value lifetime is equal to &mmut self's, so access is exclusive
// - items and len came from Vec, so total size is < isize::MAX
unsafe { std::slice::from_raw_parts_mut(self.items, self.len) }
}
fn into_raw_parts(self) -> (*mut Self::Element, usize, usize) {
(self.items, self.len, self._capacity) (self.items, self.len, self._capacity)
} }
} }

View File

@@ -57,13 +57,13 @@ pub struct TCUuidList {
/// array of uuids. these remain owned by the TCUuidList instance and will be freed by /// array of uuids. these remain owned by the TCUuidList instance and will be freed by
/// tc_uuid_list_free. This pointer is never NULL for a valid TCUuidList. /// tc_uuid_list_free. This pointer is never NULL for a valid TCUuidList.
items: *const TCUuid, items: *mut TCUuid,
} }
impl CList for TCUuidList { impl CList for TCUuidList {
type Element = TCUuid; type Element = TCUuid;
unsafe fn from_raw_parts(items: *const Self::Element, len: usize, cap: usize) -> Self { unsafe fn from_raw_parts(items: *mut Self::Element, len: usize, cap: usize) -> Self {
TCUuidList { TCUuidList {
len, len,
_capacity: cap, _capacity: cap,
@@ -71,7 +71,16 @@ impl CList for TCUuidList {
} }
} }
fn into_raw_parts(self) -> (*const Self::Element, usize, usize) { fn slice(&mut self) -> &mut [Self::Element] {
// SAFETY:
// - because we have &mut self, we have read/write access to items[0..len]
// - all items are properly initialized Element's
// - return value lifetime is equal to &mmut self's, so access is exclusive
// - items and len came from Vec, so total size is < isize::MAX
unsafe { std::slice::from_raw_parts_mut(self.items, self.len) }
}
fn into_raw_parts(self) -> (*mut Self::Element, usize, usize) {
(self.items, self.len, self._capacity) (self.items, self.len, self._capacity)
} }
} }

View File

@@ -301,7 +301,7 @@ typedef struct TCAnnotationList {
* array of annotations. these remain owned by the TCAnnotationList instance and will be freed by * array of annotations. these remain owned by the TCAnnotationList instance and will be freed by
* tc_annotation_list_free. This pointer is never NULL for a valid TCAnnotationList. * tc_annotation_list_free. This pointer is never NULL for a valid TCAnnotationList.
*/ */
const struct TCAnnotation *items; struct TCAnnotation *items;
} TCAnnotationList; } TCAnnotationList;
/** /**
@@ -333,13 +333,16 @@ typedef struct TCKVList {
* array of TCKV's. these remain owned by the TCKVList instance and will be freed by * array of TCKV's. these remain owned by the TCKVList instance and will be freed by
* tc_kv_list_free. This pointer is never NULL for a valid TCKVList. * tc_kv_list_free. This pointer is never NULL for a valid TCKVList.
*/ */
const struct TCKV *items; struct TCKV *items;
} TCKVList; } TCKVList;
/** /**
* TCTaskList represents a list of tasks. * TCTaskList represents a list of tasks.
* *
* The content of this struct must be treated as read-only. * The content of this struct must be treated as read-only: no fields or anything they reference
* should be modified directly by C code.
*
* When an item is taken from this list, its pointer in `items` is set to NULL.
*/ */
typedef struct TCTaskList { typedef struct TCTaskList {
/** /**
@@ -355,7 +358,7 @@ typedef struct TCTaskList {
* will be freed by tc_task_list_free. This pointer is never NULL for a valid TCTaskList, * will be freed by tc_task_list_free. This pointer is never NULL for a valid TCTaskList,
* and the *TCTaskList at indexes 0..len-1 are not NULL. * and the *TCTaskList at indexes 0..len-1 are not NULL.
*/ */
struct TCTask *const *items; struct TCTask **items;
} TCTaskList; } TCTaskList;
/** /**
@@ -385,7 +388,7 @@ typedef struct TCUuidList {
* array of uuids. these remain owned by the TCUuidList instance and will be freed by * array of uuids. these remain owned by the TCUuidList instance and will be freed by
* tc_uuid_list_free. This pointer is never NULL for a valid TCUuidList. * tc_uuid_list_free. This pointer is never NULL for a valid TCUuidList.
*/ */
const struct TCUuid *items; struct TCUuid *items;
} TCUuidList; } TCUuidList;
/** /**
@@ -407,7 +410,7 @@ typedef struct TCStringList {
* be freed by tc_string_list_free. This pointer is never NULL for a valid TCStringList, and the * be freed by tc_string_list_free. This pointer is never NULL for a valid TCStringList, and the
* *TCStringList at indexes 0..len-1 are not NULL. * *TCStringList at indexes 0..len-1 are not NULL.
*/ */
const struct TCString *items; struct TCString *items;
} TCStringList; } TCStringList;
/** /**
@@ -446,7 +449,7 @@ typedef struct TCUdaList {
* array of UDAs. These remain owned by the TCUdaList instance and will be freed by * array of UDAs. These remain owned by the TCUdaList instance and will be freed by
* tc_uda_list_free. This pointer is never NULL for a valid TCUdaList. * tc_uda_list_free. This pointer is never NULL for a valid TCUdaList.
*/ */
const struct TCUda *items; struct TCUda *items;
} TCUdaList; } TCUdaList;
#ifdef __cplusplus #ifdef __cplusplus
@@ -920,13 +923,26 @@ struct TCString tc_task_error(struct TCTask *task);
*/ */
void tc_task_free(struct TCTask *task); void tc_task_free(struct TCTask *task);
/**
* Take an item from a TCTaskList. After this call, the indexed item is no longer associated
* with the list and becomes the caller's responsibility, just as if it had been returned from
* `tc_replica_get_task`.
*
* The corresponding element in the `items` array will be set to NULL. If that field is already
* NULL (that is, if the item has already been taken), this function will return NULL. If the
* index is out of bounds, this function will also return NULL.
*
* The passed TCTaskList remains owned by the caller.
*/
struct TCTask *tc_task_list_take(struct TCTaskList *tasks, size_t index);
/** /**
* Free a TCTaskList instance. The instance, and all TCTaskList it contains, must not be used after * Free a TCTaskList instance. The instance, and all TCTaskList it contains, must not be used after
* this call. * this call.
* *
* When this call returns, the `items` pointer will be NULL, signalling an invalid TCTaskList. * When this call returns, the `items` pointer will be NULL, signalling an invalid TCTaskList.
*/ */
void tc_task_list_free(struct TCTaskList *tctasks); void tc_task_list_free(struct TCTaskList *tasks);
/** /**
* Free a TCUda instance. The instance, and the TCStrings it contains, must not be used * Free a TCUda instance. The instance, and the TCStrings it contains, must not be used